Spaces:
Running
Running
| # ════════════════════════════════════════════════════════════════ | |
| # SKYLINE LAND — main.py (CNN + VLM — نسخة موحدة آمنة) | |
| # ════════════════════════════════════════════════════════════════ | |
| 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() | |
| # ════════════════════════════════════════════════════════════════ | |
| # DOWNLOAD MODEL FROM HUGGING FACE | |
| # ════════════════════════════════════════════════════════════════ | |
| from huggingface_hub import hf_hub_download, snapshot_download | |
| HF_REPO = "nadjla12/skyline-api" | |
| def download_models_if_needed(): | |
| if not os.path.exists("./models/skyline_savedmodel/saved_model.pb"): | |
| print("⏳ Downloading model from Hugging Face...") | |
| os.makedirs("./models/skyline_savedmodel/variables", exist_ok=True) | |
| # تحميل الملفات واحداً واحداً في المسار الصحيح | |
| files_to_download = [ | |
| ("models/skyline_savedmodel/saved_model.pb", "./models/skyline_savedmodel/saved_model.pb"), | |
| ("models/skyline_savedmodel/variables/variables.index", "./models/skyline_savedmodel/variables/variables.index"), | |
| ("models/skyline_savedmodel/variables/variables.data-00000-of-00001", "./models/skyline_savedmodel/variables/variables.data-00000-of-00001"), | |
| ] | |
| for repo_path, local_path in files_to_download: | |
| try: | |
| downloaded = hf_hub_download( | |
| repo_id=HF_REPO, | |
| filename=repo_path, | |
| local_dir="./", | |
| ) | |
| print(f"✅ Downloaded: {repo_path}") | |
| except Exception as e: | |
| print(f"⚠️ Failed to download {repo_path}: {e}") | |
| # تحميل expert_db.json | |
| try: | |
| hf_hub_download( | |
| repo_id=HF_REPO, | |
| filename="models/expert_db.json", | |
| local_dir="./", | |
| ) | |
| print("✅ Downloaded: expert_db.json") | |
| except Exception as e: | |
| print(f"⚠️ Failed to download expert_db.json: {e}") | |
| print("✅ Model download complete!") | |
| else: | |
| print("✅ Model already exists locally.") | |
| download_models_if_needed() | |
| # ════════════════════════════════════════════════════════════════ | |
| # 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 | |
| # ════════════════════════════════════════════════════════════════ | |
| 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]) | |
| orig_buf = io.BytesIO() | |
| Image.fromarray(img_array.astype(np.uint8)).save(orig_buf, format='PNG') | |
| original_b64 = base64.b64encode(orig_buf.getvalue()).decode() | |
| 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 = 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 | |
| # ════════════════════════════════════════════════════════════════ | |
| 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=["*"], | |
| ) | |
| def root(): | |
| return { | |
| "status": "Skyline LAND API ✅", | |
| "cnn_classes": len(CLASS_NAMES), | |
| "vlm_ready": GEMINI_READY, | |
| "version": "3.0.0", | |
| } | |
| 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, | |
| } | |
| 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}") | |
| 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 | |
| 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 | |
| ] | |
| 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") | |
| 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, | |
| }) | |
| 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), | |
| ): | |
| 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"}) | |
| 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.""" | |
| 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) | |
| return JSONResponse({"success": True, "analysis": response.text, "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"}) | |
| 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), | |
| ): | |
| 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"}) | |
| sys_prompt = "أنت خبير زراعي متخصص. أجب باللغة العربية فقط وبأسلوب بسيط مناسب للمزارع." if lang == "ar" else \ | |
| "You are an expert agricultural advisor. Reply only in English, clearly and practically for a farmer." | |
| if cnn_context: | |
| try: | |
| ctx = json.loads(cnn_context) | |
| sys_prompt += f" CNN detected: '{ctx.get('disease_name_en')}' ({ctx.get('confidence', 0):.1f}% confidence)." | |
| 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:]: | |
| 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"}) | |
| 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", "") | |
| if not GEMINI_READY or gemini_model is None: | |
| fb = _build_simple_chat_fallback(message, initial_report, lang) | |
| return JSONResponse({"success": True, "response": fb, "reply": fb, "source": "fallback"}) | |
| context = f"Previous diagnosis report: {initial_report[:500]}\n\n" if initial_report else "" | |
| system_prompt = f"أنت خبير زراعي متخصص في أمراض النبات.\n{context}أجب باللغة العربية فقط، بأسلوب بسيط وعملي." if lang == "ar" else \ | |
| f"You are an expert agricultural plant pathologist.\n{context}Answer in English only, clearly and practically." | |
| 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}") | |
| fb = _build_simple_chat_fallback(message, initial_report, lang) | |
| return JSONResponse({"success": True, "response": fb, "reply": fb, "source": "error_fallback"}) | |
| except Exception as e: | |
| print(f"⚠️ Chat endpoint error: {e}") | |
| return JSONResponse({"success": False, "response": "Chat service temporarily unavailable.", "reply": "Chat service temporarily unavailable.", "source": "error"}) | |
| 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}") | |
| def _build_fallback_analysis(cnn_context: Optional[str], lang: str) -> str: | |
| if not cnn_context: | |
| return "لا يمكن الاتصال بـ Gemini VLM حالياً." if lang == "ar" else "Gemini VLM is not available." | |
| 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', 'استشر خبيراً زراعياً.')}") | |
| 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.')}") | |
| except: | |
| return "Analysis based on CNN result." | |
| def _build_fallback_chat(message: str, cnn_context: Optional[str], lang: str) -> str: | |
| 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','غير محدد')}. نوع العدوى: {ctx.get('infection_type','غير محدد')}." if ar else | |
| f"Disease cause: {ctx.get('disease_name_en','Unknown')}. 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 ("راجع بروتوكول العلاج." 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." | |
| return ctx.get('advice_ar') or ctx.get('advice_en') or ("اتبع التوصيات الموضحة في نتيجة التشخيص." if ar else "Follow the recommendations shown in the diagnosis result.") | |
| def _build_simple_chat_fallback(message: str, initial_report: str, lang: str) -> str: | |
| 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 ["علاج", "مبيد"]): | |
| return f"العلاج الموصى به: {initial_report[:200]}" if initial_report else "العلاج يعتمد على نوع المرض." | |
| 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." | |
| if any(w in msg_lower for w in ["treat", "cure", "pesticide"]): | |
| return f"Recommended treatment: {initial_report[:200]}" if initial_report else "Treatment depends on the disease type." | |
| if any(w in msg_lower for w in ["prevent", "spread"]): | |
| return "To prevent spread: remove infected plants, apply preventive fungicides." | |
| return "Thank you for your question. Please enable Gemini VLM service for detailed answers." | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) |