skyline-api / main.py
nadjla12's picture
Upload folder using huggingface_hub
03d111c verified
Raw
History Blame Contribute Delete
33.9 kB
# ════════════════════════════════════════════════════════════════
# SKYLINE LAND — main.py (CNN + VLM — نسخة موحدة آمنة)
# Admin → POST /predict
# User → POST /predict + POST /vlm-analyze + POST /vlm-chat
# كلاهما يستخدمان نفس جدول diagnostics في Supabase
# ════════════════════════════════════════════════════════════════
import os, json, io, uuid, cv2, base64
import numpy as np
from datetime import datetime
from dotenv import load_dotenv
from typing import Optional
import tensorflow as tf
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from PIL import Image
from fastapi import FastAPI, File, UploadFile, HTTPException, Form, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from supabase import create_client, Client
load_dotenv()
# ════════════════════════════════════════════════════════════════
# SUPABASE
# ════════════════════════════════════════════════════════════════
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_SERVICE_KEY = os.getenv("SUPABASE_SERVICE_KEY")
TABLE_NAME = os.getenv("TABLE_NAME", "diagnostics")
GRADCAM_BUCKET = os.getenv("GRADCAM_BUCKET", "gradcam-results")
MODEL_PATH = os.getenv("MODEL_PATH", "./models/skyline_savedmodel")
EXPERT_DB_PATH = os.getenv("EXPERT_DB_PATH", "./models/expert_db.json")
supabase: Client = create_client(SUPABASE_URL, SUPABASE_SERVICE_KEY)
print("✅ Supabase client initialized.")
# ════════════════════════════════════════════════════════════════
# GEMINI VLM — آمن: لا يكسر الـ app إذا المفتاح غير موجود
# ════════════════════════════════════════════════════════════════
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY", "")
GEMINI_READY = False
gemini_model = None
if GEMINI_API_KEY:
try:
import google.generativeai as genai
genai.configure(api_key=GEMINI_API_KEY)
gemini_model = genai.GenerativeModel("gemini-pro")
GEMINI_READY = True
print("✅ Gemini VLM initialized.")
except Exception as e:
print(f"⚠️ Gemini init failed (VLM disabled): {e}")
else:
print("⚠️ GEMINI_API_KEY not set — VLM endpoints will return fallback from CNN.")
# ════════════════════════════════════════════════════════════════
# CNN MODEL
# ════════════════════════════════════════════════════════════════
print("⏳ Loading CNN model...")
_loaded = tf.saved_model.load(MODEL_PATH)
_infer = _loaded.signatures['serving_default']
INPUT_KEY = 'input_layer_7'
OUTPUT_KEY = None
def _predict_raw(img_input: tf.Tensor) -> tf.Tensor:
global OUTPUT_KEY
result = _infer(**{INPUT_KEY: img_input})
if OUTPUT_KEY is None:
OUTPUT_KEY = list(result.keys())[0]
return result[OUTPUT_KEY]
_test = tf.zeros((1, 224, 224, 3), dtype=tf.float32)
_out = _predict_raw(_test)
print(f"✅ CNN loaded — output shape: {_out.shape}")
# ════════════════════════════════════════════════════════════════
# EXPERT DB
# ════════════════════════════════════════════════════════════════
with open(EXPERT_DB_PATH, 'r', encoding='utf-8') as f:
expert_db = json.load(f)
CLASS_NAMES = list(expert_db.keys())
HEALTHY_CLASSES = {k for k in CLASS_NAMES if 'healthy' in k.lower()}
print(f"✅ Expert DB: {len(CLASS_NAMES)} classes | {len(HEALTHY_CLASSES)} healthy")
# ════════════════════════════════════════════════════════════════
# GRAD-CAM
# ════════════════════════════════════════════════════════════════
def get_gradcam_and_visualizations(img_array: np.ndarray):
img_float = img_array.astype(np.float32)
inp = tf.keras.applications.efficientnet.preprocess_input(img_float[np.newaxis])
inp_var = tf.Variable(inp, trainable=True, dtype=tf.float32)
with tf.GradientTape() as tape:
tape.watch(inp_var)
preds = _predict_raw(inp_var)
pred_idx = int(tf.argmax(preds[0]))
score = preds[:, pred_idx]
grads = tape.gradient(score, inp_var)
heatmap = tf.reduce_mean(tf.abs(grads[0]), axis=-1).numpy()
if heatmap.max() > 0:
heatmap = heatmap / heatmap.max()
confidence = float(preds[0][pred_idx])
# Original → base64
orig_buf = io.BytesIO()
Image.fromarray(img_array.astype(np.uint8)).save(orig_buf, format='PNG')
original_b64 = base64.b64encode(orig_buf.getvalue()).decode()
# Heatmap → base64
hm_resized = cv2.resize(heatmap, (224, 224))
hm_colored = (plt.cm.jet(hm_resized)[:, :, :3] * 255).astype(np.uint8)
hm_buf = io.BytesIO()
Image.fromarray(hm_colored).save(hm_buf, format='PNG')
heatmap_b64 = base64.b64encode(hm_buf.getvalue()).decode()
# Overlay → base64
overlay = np.clip(hm_colored * 0.55 + img_array * 0.45, 0, 255).astype(np.uint8)
ov_buf = io.BytesIO()
Image.fromarray(overlay).save(ov_buf, format='PNG')
overlay_b64 = base64.b64encode(ov_buf.getvalue()).decode()
return heatmap, pred_idx, confidence, original_b64, heatmap_b64, overlay_b64
def build_gradcam_png_bytes(heatmap_np, img_array, is_healthy) -> bytes:
fig, axes = plt.subplots(1, 3, figsize=(15, 5), facecolor='white')
for ax in axes: ax.axis('off')
axes[0].imshow(img_array.astype(np.uint8)); axes[0].set_title('Original')
if is_healthy:
axes[1].imshow(img_array.astype(np.uint8)); axes[1].set_title('Healthy')
axes[2].imshow(img_array.astype(np.uint8)); axes[2].set_title('Healthy')
else:
hm = cv2.resize(heatmap_np, (224, 224))
sup = np.clip(plt.cm.jet(hm)[:, :, :3] * 0.55 + img_array / 255.0 * 0.45, 0, 1)
axes[1].imshow(hm, cmap='jet'); axes[1].set_title('Grad-CAM')
axes[2].imshow(sup); axes[2].set_title('Overlay')
buf = io.BytesIO()
plt.tight_layout(); fig.savefig(buf, format='png', dpi=150, facecolor='white')
plt.close(fig); buf.seek(0)
return buf.read()
# ════════════════════════════════════════════════════════════════
# STORAGE
# ════════════════════════════════════════════════════════════════
def upload_gradcam_to_storage(png_bytes: bytes, filename: str) -> Optional[str]:
try:
path = f"overlays/{filename}"
supabase.storage.from_(GRADCAM_BUCKET).upload(
path=path, file=png_bytes,
file_options={"content-type": "image/png", "upsert": "true"}
)
url = supabase.storage.from_(GRADCAM_BUCKET).get_public_url(path)
print(f"✅ Storage: {path}")
return url
except Exception as e:
print(f"⚠️ Storage upload failed: {e}")
return None
# ════════════════════════════════════════════════════════════════
# HELPER — حفظ في Supabase (مشترك بين Admin و User)
# ════════════════════════════════════════════════════════════════
def save_diagnosis(record: dict) -> bool:
try:
result = supabase.table(TABLE_NAME).insert(record).execute()
if result.data:
print(f"✅ DB saved: {record.get('class_key')} | user={record.get('profile_id')} | conf={record.get('confidence')}%")
return True
print(f"⚠️ Insert returned no data")
return False
except Exception as e:
print(f"⚠️ DB insert failed: {e}")
return False
# ════════════════════════════════════════════════════════════════
# FASTAPI APP
# ════════════════════════════════════════════════════════════════
app = FastAPI(title="Skyline LAND API", version="3.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
# ── GET / ─────────────────────────────────────────────────────────
@app.get("/")
def root():
return {
"status": "Skyline LAND API ✅",
"cnn_classes": len(CLASS_NAMES),
"vlm_ready": GEMINI_READY,
"version": "3.0.0",
}
# ── GET /health ───────────────────────────────────────────────────
@app.get("/health")
def health():
db_ok = False
try:
supabase.table(TABLE_NAME).select("id").limit(1).execute()
db_ok = True
except: pass
return {
"api": "ok",
"db": "ok" if db_ok else "error",
"cnn": f"{len(CLASS_NAMES)} classes",
"vlm": "ready" if GEMINI_READY else "disabled (no API key)",
"bucket": GRADCAM_BUCKET,
}
# ════════════════════════════════════════════════════════════════
# POST /predict ← Admin + User (CNN + Grad-CAM)
# كلا الصفحتين تستدعي هذا الـ endpoint
# ════════════════════════════════════════════════════════════════
@app.post("/predict")
async def predict(
file: UploadFile = File(...),
user_id: Optional[str] = Form(None),
):
if not file.content_type.startswith("image/"):
raise HTTPException(400, "File must be an image")
raw = await file.read()
try:
pil_img = Image.open(io.BytesIO(raw)).convert("RGB").resize((224, 224))
img_array = np.array(pil_img, dtype=np.uint8)
except Exception as e:
raise HTTPException(400, f"Cannot open image: {e}")
# CNN + Grad-CAM
heatmap_np, class_idx, conf_raw, original_b64, heatmap_b64, overlay_b64 = \
get_gradcam_and_visualizations(img_array)
conf = round(conf_raw * 100, 2)
class_key = CLASS_NAMES[class_idx] if class_idx < len(CLASS_NAMES) else f"class_{class_idx}"
info = expert_db.get(class_key, {})
is_healthy = class_key in HEALTHY_CLASSES
# Top-3
inp_np = tf.keras.applications.efficientnet.preprocess_input(img_array.astype(np.float32)[np.newaxis])
all_preds = _predict_raw(inp_np)[0].numpy()
top3_idx = np.argsort(all_preds)[-3:][::-1]
top3 = [
{
"class": CLASS_NAMES[i],
"name_en": expert_db.get(CLASS_NAMES[i], {}).get("en", CLASS_NAMES[i]),
"conf": round(float(all_preds[i]) * 100, 2),
}
for i in top3_idx
]
# Storage upload (فقط للنبات المريض)
gradcam_url = None
if not is_healthy:
gradcam_png = build_gradcam_png_bytes(heatmap_np, img_array, is_healthy)
gradcam_url = upload_gradcam_to_storage(gradcam_png, f"{uuid.uuid4()}.png")
# حفظ في Supabase — نفس الجدول للـ Admin والـ User
record = {
"profile_id": user_id,
"class_key": class_key,
"disease_name_en": info.get("en", class_key),
"disease_name_ar": info.get("ar", ""),
"advice_en": info.get("adv_en", ""),
"advice_ar": info.get("adv_ar", ""),
"treatment_en": info.get("chem_en", ""),
"treatment_ar": info.get("chem_ar", ""),
"infection_type": info.get("infection_type", None),
"is_healthy": is_healthy,
"confidence": conf,
"gradcam_url": gradcam_url,
"analyzed_at": datetime.utcnow().isoformat(),
}
saved_to_db = save_diagnosis(record)
return JSONResponse({
"success": True,
"saved_to_db": saved_to_db,
"class_key": class_key,
"disease_name_en": record["disease_name_en"],
"disease_name_ar": record["disease_name_ar"],
"confidence": conf,
"badge": "High" if conf >= 90 else "Good" if conf >= 75 else "Medium" if conf >= 60 else "Low",
"is_healthy": is_healthy,
"advice_en": record["advice_en"],
"advice_ar": record["advice_ar"],
"treatment_en": record["treatment_en"],
"treatment_ar": record["treatment_ar"],
"infection_type": record["infection_type"],
"gradcam_url": gradcam_url,
"original_b64": original_b64,
"heatmap_b64": heatmap_b64,
"overlay_b64": overlay_b64,
"top3": top3,
})
# ════════════════════════════════════════════════════════════════
# POST /vlm-analyze ← User فقط (تحليل أولي بـ Gemini Vision)
# ════════════════════════════════════════════════════════════════
@app.post("/vlm-analyze")
async def vlm_analyze(
file: Optional[UploadFile] = File(None),
lang: str = Form("en"),
cnn_context: Optional[str] = Form(None),
user_id: Optional[str] = Form(None),
):
# ── Fallback إذا Gemini غير جاهز ─────────────────────────────
if not GEMINI_READY or gemini_model is None:
fallback = _build_fallback_analysis(cnn_context, lang)
return JSONResponse({"success": True, "analysis": fallback, "source": "cnn_fallback"})
# ── Prompt حسب اللغة ─────────────────────────────────────────
if lang == "ar":
prompt = """أنت خبير زراعي متخصص في أمراض النبات والمحاصيل.
حلّل الصورة وقدّم تقريراً شاملاً ومنظماً يشمل:
**1. التشخيص:** ما المرض أو الحالة الصحية التي تراها؟
**2. الأعراض المرئية:** صف ما يظهر في الصورة بدقة
**3. السبب:** الممرض أو العامل البيئي المسؤول
**4. درجة الخطورة:** منخفضة / متوسطة / مرتفعة — ولماذا؟
**5. خطر الانتشار:** هل يمكن أن يصيب نباتات مجاورة؟
**6. العلاج الفوري:** خطوات عملية يبدأ بها المزارع الآن
**7. الوقاية:** كيف يتجنب تكرار المشكلة في المواسم القادمة
أجب باللغة العربية الفصحى البسيطة المناسبة للمزارع الجزائري."""
else:
prompt = """You are an expert agricultural plant pathologist.
Analyze this plant/field image and provide a structured comprehensive report:
**1. Diagnosis:** What disease or health condition is present?
**2. Visible Symptoms:** Describe exactly what appears in the image
**3. Cause:** Pathogen or environmental factor responsible
**4. Severity Level:** Low / Medium / High — and why?
**5. Spread Risk:** Can it infect neighboring plants?
**6. Immediate Treatment:** Practical steps the farmer can take now
**7. Prevention:** How to avoid recurrence in future seasons
Be thorough, practical, and clear for a farming context."""
# إضافة سياق CNN إذا متوفر
if cnn_context:
try:
ctx = json.loads(cnn_context)
cnn_info = (
f"\n\n[CNN Pre-detection: '{ctx.get('disease_name_en')}' "
f"({ctx.get('confidence', 0):.1f}% confidence) — "
f"{'Healthy' if ctx.get('is_healthy') else 'Diseased'}]"
)
prompt += cnn_info
except: pass
try:
parts = [prompt]
if file:
raw = await file.read()
parts.append({"mime_type": file.content_type or "image/jpeg", "data": raw})
response = gemini_model.generate_content(parts)
analysis = response.text
return JSONResponse({
"success": True,
"analysis": analysis,
"source": "gemini_vlm",
})
except Exception as e:
print(f"⚠️ Gemini VLM error: {e}")
fallback = _build_fallback_analysis(cnn_context, lang)
return JSONResponse({"success": True, "analysis": fallback, "source": "cnn_fallback"})
# ════════════════════════════════════════════════════════════════
# POST /vlm-chat ← User فقط (دردشة متعددة الأدوار)
# ════════════════════════════════════════════════════════════════
@app.post("/vlm-chat")
async def vlm_chat(
message: str = Form(...),
lang: str = Form("en"),
history: Optional[str] = Form(None),
file: Optional[UploadFile] = File(None),
cnn_context: Optional[str] = Form(None),
):
# ── Fallback إذا Gemini غير جاهز ─────────────────────────────
if not GEMINI_READY or gemini_model is None:
reply = _build_fallback_chat(message, cnn_context, lang)
return JSONResponse({"success": True, "reply": reply, "source": "cnn_fallback"})
# ── System prompt ─────────────────────────────────────────────
if lang == "ar":
sys_prompt = "أنت خبير زراعي متخصص. أجب باللغة العربية فقط وبأسلوب بسيط مناسب للمزارع."
else:
sys_prompt = "You are an expert agricultural advisor. Reply only in English, clearly and practically for a farmer."
# إضافة سياق CNN
if cnn_context:
try:
ctx = json.loads(cnn_context)
ctx_txt = (
f" CNN detected: '{ctx.get('disease_name_en')}' "
f"({ctx.get('confidence', 0):.1f}% confidence)."
)
sys_prompt += ctx_txt
except: pass
# بناء تاريخ المحادثة
gemini_history = [
{"role": "user", "parts": [sys_prompt]},
{"role": "model", "parts": ["Understood. I'm your agricultural expert assistant."]},
]
if history:
try:
hist = json.loads(history)
for msg in hist[-6:]: # آخر 6 رسائل فقط لتوفير التوكن
role = "user" if msg["role"] == "user" else "model"
gemini_history.append({"role": role, "parts": [msg["content"]]})
except: pass
try:
chat = gemini_model.start_chat(history=gemini_history)
parts = [message]
if file:
raw = await file.read()
parts.append({"mime_type": file.content_type or "image/jpeg", "data": raw})
response = chat.send_message(parts)
return JSONResponse({
"success": True,
"reply": response.text,
"source": "gemini_vlm",
})
except Exception as e:
print(f"⚠️ Gemini chat error: {e}")
reply = _build_fallback_chat(message, cnn_context, lang)
return JSONResponse({"success": True, "reply": reply, "source": "cnn_fallback"})
# ════════════════════════════════════════════════════════════════
# POST /chat ← دردشة بسيطة (متوافق مع HTML user)
# ════════════════════════════════════════════════════════════════
@app.post("/chat")
async def chat_simple(request: Request):
try:
body = await request.json()
message = body.get("message", "")
lang = body.get("lang", "en")
history = body.get("history", [])
initial_report = body.get("initial_report", "")
# Fallback إذا Gemini غير جاهز
if not GEMINI_READY or gemini_model is None:
return JSONResponse({
"success": True,
"response": _build_simple_chat_fallback(message, initial_report, lang),
"reply": _build_simple_chat_fallback(message, initial_report, lang),
"source": "fallback"
})
# بناء السياق
context = ""
if initial_report:
context = f"Previous diagnosis report: {initial_report[:500]}\n\n"
if lang == "ar":
system_prompt = f"""أنت خبير زراعي متخصص في أمراض النبات.
{context}
أجب على سؤال المزارع باللغة العربية فقط، بأسلوب بسيط وعملي.
قدم نصائح واضحة حول العلاج والوقاية."""
else:
system_prompt = f"""You are an expert agricultural plant pathologist.
{context}
Answer the farmer's question in English only, in a clear and practical way.
Provide specific advice on treatment and prevention."""
# بناء تاريخ المحادثة
gemini_history = [
{"role": "user", "parts": [system_prompt]},
{"role": "model", "parts": ["Understood. I'm your agricultural expert assistant. How can I help you today?"]},
]
for msg in history[-10:]:
role = "user" if msg.get("role") == "user" else "model"
gemini_history.append({"role": role, "parts": [msg.get("content", "")]})
try:
chat = gemini_model.start_chat(history=gemini_history)
response = chat.send_message(message)
return JSONResponse({
"success": True,
"response": response.text,
"reply": response.text,
"source": "gemini_vlm"
})
except Exception as e:
print(f"⚠️ Gemini chat error: {e}")
fallback = _build_simple_chat_fallback(message, initial_report, lang)
return JSONResponse({
"success": True,
"response": fallback,
"reply": fallback,
"source": "error_fallback"
})
except Exception as e:
print(f"⚠️ Chat endpoint error: {e}")
return JSONResponse({
"success": False,
"response": "Chat service temporarily unavailable. Please try again later.",
"reply": "Chat service temporarily unavailable. Please try again later.",
"source": "error"
})
# ════════════════════════════════════════════════════════════════
# GET /history ← Admin (كل البيانات) + User (بياناته فقط)
# ════════════════════════════════════════════════════════════════
@app.get("/history")
def history(profile_id: Optional[str] = None, limit: int = 20):
try:
query = supabase.table(TABLE_NAME).select(
"id, profile_id, class_key, disease_name_en, disease_name_ar, "
"confidence, is_healthy, gradcam_url, analyzed_at"
)
if profile_id:
query = query.eq("profile_id", profile_id)
response = query.order("analyzed_at", desc=True).limit(limit).execute()
return {"success": True, "count": len(response.data), "data": response.data}
except Exception as e:
raise HTTPException(500, f"DB error: {e}")
# ════════════════════════════════════════════════════════════════
# HELPERS — Fallback بدون Gemini (يستخدم نتيجة CNN)
# ════════════════════════════════════════════════════════════════
def _build_fallback_analysis(cnn_context: Optional[str], lang: str) -> str:
"""يبني تحليلاً من نتيجة CNN عندما يكون Gemini غير متاح"""
if not cnn_context:
return ("لا يمكن الاتصال بـ Gemini VLM حالياً. يرجى استخدام نتيجة CNN أعلاه."
if lang == "ar" else
"Gemini VLM is not available. Please use the CNN result above.")
try:
ctx = json.loads(cnn_context)
if lang == "ar":
return (
f"**التشخيص:** {ctx.get('disease_name_ar') or ctx.get('disease_name_en', '—')}\n\n"
f"**نسبة الثقة:** {ctx.get('confidence', 0):.1f}%\n\n"
f"**الحالة:** {'نبات سليم ✅' if ctx.get('is_healthy') else 'مصاب ⚠️'}\n\n"
f"**النصيحة:** {ctx.get('advice_ar') or ctx.get('advice_en', 'راجع بروتوكول العلاج.')}\n\n"
f"**العلاج:** {ctx.get('treatment_ar') or ctx.get('treatment_en', 'استشر خبيراً زراعياً.')}\n\n"
f"_ملاحظة: هذا التحليل مبني على نتيجة CNN. قم بإضافة GEMINI_API_KEY للحصول على تحليل VLM كامل._"
)
else:
return (
f"**Diagnosis:** {ctx.get('disease_name_en', '—')}\n\n"
f"**Confidence:** {ctx.get('confidence', 0):.1f}%\n\n"
f"**Status:** {'Healthy ✅' if ctx.get('is_healthy') else 'Diseased ⚠️'}\n\n"
f"**Advice:** {ctx.get('advice_en', 'Follow the treatment protocol.')}\n\n"
f"**Treatment:** {ctx.get('treatment_en', 'Consult an agricultural expert.')}\n\n"
f"_Note: This analysis is based on CNN result. Add GEMINI_API_KEY for full VLM analysis._"
)
except:
return "Analysis based on CNN result. Add GEMINI_API_KEY for VLM capabilities."
def _build_fallback_chat(message: str, cnn_context: Optional[str], lang: str) -> str:
"""يرد على الأسئلة من نتيجة CNN عندما يكون Gemini غير متاح"""
ctx = {}
if cnn_context:
try: ctx = json.loads(cnn_context)
except: pass
msg_lower = message.lower()
ar = lang == "ar"
if any(w in msg_lower for w in ["cause","سبب","ممرض","pathogen"]):
return (f"سبب المرض: {ctx.get('disease_name_ar') or ctx.get('disease_name_en','غير محدد')}. "
f"نوع العدوى: {ctx.get('infection_type','غير محدد')}." if ar else
f"Disease cause: {ctx.get('disease_name_en','Unknown')}. "
f"Infection type: {ctx.get('infection_type','Unknown')}.")
if any(w in msg_lower for w in ["treat","علاج","cure","دواء","مبيد"]):
return (ctx.get('treatment_ar') or ctx.get('treatment_en') or
("راجع بروتوكول العلاج في نتيجة CNN." if ar else "Refer to the CNN treatment protocol."))
if any(w in msg_lower for w in ["prevent","وقاية","spread","انتشار"]):
return ("لمنع الانتشار: أزل النباتات المصابة، طبّق مبيدات وقائية، وحسّن تهوية الحقل." if ar else
"To prevent spread: remove infected plants, apply preventive fungicides, improve field ventilation.")
if any(w in msg_lower for w in ["advice","نصيحة","recommend","توصية"]):
return (ctx.get('advice_ar') or ctx.get('advice_en') or
("اتبع التوصيات الموضحة في نتيجة التشخيص." if ar else
"Follow the recommendations shown in the diagnosis result."))
return (ctx.get('advice_ar') or ctx.get('advice_en') or
("يرجى إضافة GEMINI_API_KEY لتفعيل الدردشة الذكية الكاملة." if ar else
"Please add GEMINI_API_KEY to enable full AI chat capabilities."))
def _build_simple_chat_fallback(message: str, initial_report: str, lang: str) -> str:
"""رد بديل بسيط عندما يكون Gemini غير متاح"""
msg_lower = message.lower()
if lang == "ar":
if any(w in msg_lower for w in ["سبب", "لماذا"]):
return "سبب المرض عادة ما يكون فطرياً أو بكتيرياً أو فيروسياً. يرجى الاطلاع على تقرير التشخيص أعلاه."
if any(w in msg_lower for w in ["علاج", "مبيد"]):
if initial_report:
return f"العلاج الموصى به: {initial_report[:200]}"
return "العلاج يعتمد على نوع المرض. يرجى استشارة خبير زراعي."
if any(w in msg_lower for w in ["منع", "وقاية"]):
return "للوقاية: أزل النباتات المصابة، استخدم مبيدات وقائية، وحسن تهوية الحقل."
return "شكراً لسؤالك. للحصول على إجابة مفصلة، يرجى تشغيل خدمة Gemini VLM."
else:
if any(w in msg_lower for w in ["cause", "why"]):
return "The disease is typically caused by fungal, bacterial, or viral pathogens. Check the diagnosis report above."
if any(w in msg_lower for w in ["treat", "cure", "pesticide"]):
if initial_report:
return f"Recommended treatment: {initial_report[:200]}"
return "Treatment depends on the disease type. Please consult an agricultural expert."
if any(w in msg_lower for w in ["prevent", "spread"]):
return "To prevent spread: remove infected plants, apply preventive fungicides, improve field ventilation."
return "Thank you for your question. For a detailed answer, please enable Gemini VLM service."
# ════════════════════════════════════════════════════════════════
# RUN
# ════════════════════════════════════════════════════════════════
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)