Spaces:
Sleeping
Sleeping
shahad altamimi commited on
Commit ·
96d14d2
1
Parent(s): b1981c6
update
Browse files
reverse-dictionary/backend/app/ratings.py
DELETED
|
@@ -1,124 +0,0 @@
|
|
| 1 |
-
# ============================================================
|
| 2 |
-
# 📄 الملف: app/ratings.py
|
| 3 |
-
# 🎯 الغرض: إدارة سجل التقييمات (حفظ / قراءة / حذف / إحصاء).
|
| 4 |
-
#
|
| 5 |
-
# كل تقييم يُحفظ كصف في ملف CSV (ratings.csv) بترميز utf-8-sig
|
| 6 |
-
# عشان العربية تظهر صح في Excel. هذا الملف يوفّر كل العمليات
|
| 7 |
-
# على السجل: إضافة تقييم، جلب السجل كامل، حذف صفوف محدّدة،
|
| 8 |
-
# حذف الكل، وحساب ملخّص أداء النماذج.
|
| 9 |
-
# ============================================================
|
| 10 |
-
|
| 11 |
-
import os
|
| 12 |
-
from datetime import datetime
|
| 13 |
-
|
| 14 |
-
import pandas as pd
|
| 15 |
-
|
| 16 |
-
import config
|
| 17 |
-
|
| 18 |
-
# أسماء الأعمدة في ملف التقييمات
|
| 19 |
-
COLUMNS = ["التاريخ والوقت", "الاستعلام", "النموذج", "التقييم",
|
| 20 |
-
"Threshold", "أفضل كلمة", "أعلى تشابه"]
|
| 21 |
-
REQUIRED = ["التاريخ والوقت", "الاستعلام", "النموذج", "التقييم"]
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
# ------------------------------------------------------------
|
| 25 |
-
# 💾 حفظ تقييم جديد (إضافة صف للملف)
|
| 26 |
-
# ------------------------------------------------------------
|
| 27 |
-
def save_rating(query, model_display_name, rating, threshold,
|
| 28 |
-
top_lemma=None, top_score=None) -> None:
|
| 29 |
-
row = {
|
| 30 |
-
"التاريخ والوقت": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 31 |
-
"الاستعلام": query,
|
| 32 |
-
"النموذج": model_display_name,
|
| 33 |
-
"التقييم": rating,
|
| 34 |
-
"Threshold": threshold,
|
| 35 |
-
"أفضل كلمة": top_lemma or "",
|
| 36 |
-
"أعلى تشابه": round(top_score, 3) if top_score else "",
|
| 37 |
-
}
|
| 38 |
-
write_header = not config.RATINGS_PATH.exists()
|
| 39 |
-
pd.DataFrame([row]).to_csv(
|
| 40 |
-
config.RATINGS_PATH, mode="a", header=write_header,
|
| 41 |
-
index=False, encoding="utf-8-sig",
|
| 42 |
-
)
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
# ------------------------------------------------------------
|
| 46 |
-
# 📖 قراءة كل التقييمات (مع تنظيف الملف التالف تلقائيًا)
|
| 47 |
-
# ------------------------------------------------------------
|
| 48 |
-
def load_ratings() -> pd.DataFrame:
|
| 49 |
-
if not config.RATINGS_PATH.exists():
|
| 50 |
-
return pd.DataFrame(columns=COLUMNS)
|
| 51 |
-
try:
|
| 52 |
-
df = pd.read_csv(config.RATINGS_PATH, encoding="utf-8-sig")
|
| 53 |
-
# لو الملف ناقص أعمدة أساسية نعتبره تالفًا ونحذفه
|
| 54 |
-
if not all(c in df.columns for c in REQUIRED):
|
| 55 |
-
config.RATINGS_PATH.unlink()
|
| 56 |
-
return pd.DataFrame(columns=COLUMNS)
|
| 57 |
-
return df
|
| 58 |
-
except Exception:
|
| 59 |
-
config.RATINGS_PATH.unlink()
|
| 60 |
-
return pd.DataFrame(columns=COLUMNS)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
# ------------------------------------------------------------
|
| 64 |
-
# 🗑️ حذف تقييمات محدّدة (حسب مفاتيح: التاريخ + الاستعلام + النموذج)
|
| 65 |
-
# ------------------------------------------------------------
|
| 66 |
-
def delete_ratings(keys: list[dict]) -> int:
|
| 67 |
-
"""keys = [{datetime, query, model}, ...]. يرجّع عدد الصفوف المحذوفة."""
|
| 68 |
-
df = load_ratings()
|
| 69 |
-
if df.empty:
|
| 70 |
-
return 0
|
| 71 |
-
|
| 72 |
-
key_tuples = {(k["datetime"], k["query"], k["model"]) for k in keys}
|
| 73 |
-
mask = df[["التاريخ والوقت", "الاستعلام", "النموذج"]].apply(tuple, axis=1).isin(key_tuples)
|
| 74 |
-
deleted_count = int(mask.sum())
|
| 75 |
-
|
| 76 |
-
remaining = df[~mask]
|
| 77 |
-
if len(remaining) > 0:
|
| 78 |
-
remaining.to_csv(config.RATINGS_PATH, index=False, encoding="utf-8-sig")
|
| 79 |
-
elif config.RATINGS_PATH.exists():
|
| 80 |
-
config.RATINGS_PATH.unlink()
|
| 81 |
-
return deleted_count
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
# ------------------------------------------------------------
|
| 85 |
-
# ⚠️ حذف كل التقييمات
|
| 86 |
-
# ------------------------------------------------------------
|
| 87 |
-
def delete_all_ratings() -> None:
|
| 88 |
-
if config.RATINGS_PATH.exists():
|
| 89 |
-
config.RATINGS_PATH.unlink()
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
# ------------------------------------------------------------
|
| 93 |
-
# 📊 ملخّص أداء النماذج + إحصاءات عامة
|
| 94 |
-
# ------------------------------------------------------------
|
| 95 |
-
def get_summary() -> dict:
|
| 96 |
-
df = load_ratings()
|
| 97 |
-
if df.empty:
|
| 98 |
-
return {"stats": {}, "performance": []}
|
| 99 |
-
|
| 100 |
-
# إحصاءات عامة
|
| 101 |
-
stats = {
|
| 102 |
-
"total": len(df),
|
| 103 |
-
"ممتاز": int((df["التقييم"] == "ممتاز").sum()),
|
| 104 |
-
"جيد": int((df["التقييم"] == "جيد").sum()),
|
| 105 |
-
"ضعيف": int((df["التقييم"] == "ضعيف").sum()),
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
# أداء كل نموذج
|
| 109 |
-
performance = []
|
| 110 |
-
for model_name, group in df.groupby("النموذج"):
|
| 111 |
-
excellent = int((group["التقييم"] == "ممتاز").sum())
|
| 112 |
-
good = int((group["التقييم"] == "جيد").sum())
|
| 113 |
-
weak = int((group["التقييم"] == "ضعيف").sum())
|
| 114 |
-
total = excellent + good + weak
|
| 115 |
-
performance.append({
|
| 116 |
-
"model": model_name,
|
| 117 |
-
"ممتاز": excellent,
|
| 118 |
-
"جيد": good,
|
| 119 |
-
"ضعيف": weak,
|
| 120 |
-
"الإجمالي": total,
|
| 121 |
-
"نسبة الممتاز": round(excellent / total * 100, 1) if total else 0.0,
|
| 122 |
-
})
|
| 123 |
-
|
| 124 |
-
return {"stats": stats, "performance": performance}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reverse-dictionary/backend/app/routes.py
DELETED
|
@@ -1,96 +0,0 @@
|
|
| 1 |
-
# ============================================================
|
| 2 |
-
# 📄 الملف: app/routes.py
|
| 3 |
-
# 🎯 الغرض: تعريف نقاط النهاية (Endpoints) للـ API.
|
| 4 |
-
#
|
| 5 |
-
# هذا الملف يربط بين طلبات HTTP القادمة من الـ frontend
|
| 6 |
-
# ومنطق المشروع (البحث، التقييمات). كل دالة هنا تمثّل
|
| 7 |
-
# "عنوانًا" يستدعيه الـ frontend:
|
| 8 |
-
#
|
| 9 |
-
# GET /api/models → قائمة النماذج
|
| 10 |
-
# POST /api/search → تنفيذ بحث
|
| 11 |
-
# GET /api/ratings → جلب كل التقييمات + الملخّص
|
| 12 |
-
# POST /api/ratings → حفظ تقييم
|
| 13 |
-
# POST /api/ratings/delete → حذف تقييمات محدّدة
|
| 14 |
-
# DELETE /api/ratings → حذف كل التقييمات
|
| 15 |
-
# ============================================================
|
| 16 |
-
|
| 17 |
-
from fastapi import APIRouter
|
| 18 |
-
|
| 19 |
-
import config
|
| 20 |
-
from app import ratings as ratings_service
|
| 21 |
-
from app.search import search_all_models
|
| 22 |
-
from app.schemas import (
|
| 23 |
-
DeleteRatingsRequest,
|
| 24 |
-
ModelInfo,
|
| 25 |
-
RatingRequest,
|
| 26 |
-
SearchRequest,
|
| 27 |
-
SearchResponse,
|
| 28 |
-
)
|
| 29 |
-
|
| 30 |
-
router = APIRouter(prefix="/api")
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
# ------------------------------------------------------------
|
| 34 |
-
# 🤖 قائمة النماذج
|
| 35 |
-
# ------------------------------------------------------------
|
| 36 |
-
@router.get("/models", response_model=list[ModelInfo])
|
| 37 |
-
def get_models():
|
| 38 |
-
return [
|
| 39 |
-
ModelInfo(model_index=i, model_key=m["key"], display_name=m["display_name"])
|
| 40 |
-
for i, m in enumerate(config.MODELS)
|
| 41 |
-
]
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
# ------------------------------------------------------------
|
| 45 |
-
# 🔎 البحث
|
| 46 |
-
# ------------------------------------------------------------
|
| 47 |
-
@router.post("/search", response_model=SearchResponse)
|
| 48 |
-
def search(request: SearchRequest):
|
| 49 |
-
results = search_all_models(request.query.strip(), request.threshold,
|
| 50 |
-
rerank=request.rerank)
|
| 51 |
-
return SearchResponse(
|
| 52 |
-
query=request.query,
|
| 53 |
-
threshold=request.threshold,
|
| 54 |
-
results=results,
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
# ------------------------------------------------------------
|
| 59 |
-
# ⭐ التقييمات
|
| 60 |
-
# ------------------------------------------------------------
|
| 61 |
-
@router.get("/ratings")
|
| 62 |
-
def get_ratings():
|
| 63 |
-
df = ratings_service.load_ratings()
|
| 64 |
-
summary = ratings_service.get_summary()
|
| 65 |
-
return {
|
| 66 |
-
"ratings": df.to_dict(orient="records"),
|
| 67 |
-
"stats": summary["stats"],
|
| 68 |
-
"performance": summary["performance"],
|
| 69 |
-
}
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
@router.post("/ratings")
|
| 73 |
-
def add_rating(request: RatingRequest):
|
| 74 |
-
ratings_service.save_rating(
|
| 75 |
-
query=request.query,
|
| 76 |
-
model_display_name=request.model_display_name,
|
| 77 |
-
rating=request.rating,
|
| 78 |
-
threshold=request.threshold,
|
| 79 |
-
top_lemma=request.top_lemma,
|
| 80 |
-
top_score=request.top_score,
|
| 81 |
-
)
|
| 82 |
-
return {"status": "ok", "message": "تم حفظ التقييم"}
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
@router.post("/ratings/delete")
|
| 86 |
-
def delete_selected(request: DeleteRatingsRequest):
|
| 87 |
-
keys = [{"datetime": k.datetime, "query": k.query, "model": k.model}
|
| 88 |
-
for k in request.keys]
|
| 89 |
-
count = ratings_service.delete_ratings(keys)
|
| 90 |
-
return {"status": "ok", "deleted": count}
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
@router.delete("/ratings")
|
| 94 |
-
def delete_all():
|
| 95 |
-
ratings_service.delete_all_ratings()
|
| 96 |
-
return {"status": "ok", "message": "تم حذف جميع التقييمات"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reverse-dictionary/backend/app/schemas.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
| 1 |
-
# ============================================================
|
| 2 |
-
# 📄 الملف: app/schemas.py
|
| 3 |
-
# 🎯 الغرض: تعريف أشكال البيانات (Schemas) الداخلة والخارجة من الـ API.
|
| 4 |
-
#
|
| 5 |
-
# نستخدم Pydantic عشان:
|
| 6 |
-
# 1. التحقّق التلقائي من صحة البيانات القادمة من الـ frontend.
|
| 7 |
-
# 2. توليد توثيق تلقائي للـ API (صفحة /docs).
|
| 8 |
-
# 3. توضيح "العقد" بين الواجهة والخادم: وش يرسل ووش يستقبل.
|
| 9 |
-
# ============================================================
|
| 10 |
-
|
| 11 |
-
from typing import List, Optional
|
| 12 |
-
|
| 13 |
-
from pydantic import BaseModel, Field
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
# ------------------------------------------------------------
|
| 17 |
-
# 🔎 طلبات واستجابات البحث
|
| 18 |
-
# ------------------------------------------------------------
|
| 19 |
-
class SearchRequest(BaseModel):
|
| 20 |
-
"""طلب البحث القادم من الواجهة."""
|
| 21 |
-
query: str = Field(..., description="التعريف أو النص المُراد البحث عنه")
|
| 22 |
-
threshold: float = Field(0.70, ge=0.0, le=1.0, description="حد التشابه الأدنى")
|
| 23 |
-
rerank: bool = Field(False, description="تفعيل إعادة الترتيب بالـ Cross-Encoder")
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
class ResultItem(BaseModel):
|
| 27 |
-
"""نتيجة واحدة (كلمة) داخل قائمة نتائج نموذج."""
|
| 28 |
-
lemma: str
|
| 29 |
-
definition: str
|
| 30 |
-
example: str = ""
|
| 31 |
-
pos: str = ""
|
| 32 |
-
score: float
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
class ModelResult(BaseModel):
|
| 36 |
-
"""نتائج نموذج واحد كاملة."""
|
| 37 |
-
model_index: int
|
| 38 |
-
model_key: str
|
| 39 |
-
display_name: str
|
| 40 |
-
items: List[ResultItem]
|
| 41 |
-
top_lemma: Optional[str] = None
|
| 42 |
-
top_score: Optional[float] = None
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
class SearchResponse(BaseModel):
|
| 46 |
-
"""الاستجابة الكاملة للبحث (نتائج النماذج الثلاثة)."""
|
| 47 |
-
query: str
|
| 48 |
-
threshold: float
|
| 49 |
-
results: List[ModelResult]
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
# ------------------------------------------------------------
|
| 53 |
-
# ⭐ طلبات واستجابات التقييمات
|
| 54 |
-
# ------------------------------------------------------------
|
| 55 |
-
class RatingRequest(BaseModel):
|
| 56 |
-
"""طلب حفظ تقييم لنتيجة نموذج."""
|
| 57 |
-
query: str
|
| 58 |
-
model_display_name: str
|
| 59 |
-
rating: str = Field(..., description="ممتاز | جيد | ضعيف")
|
| 60 |
-
threshold: float
|
| 61 |
-
top_lemma: Optional[str] = ""
|
| 62 |
-
top_score: Optional[float] = None
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
class RatingKey(BaseModel):
|
| 66 |
-
"""مفتاح يُعرّف صف تقييم بشكل فريد (للحذف)."""
|
| 67 |
-
datetime: str
|
| 68 |
-
query: str
|
| 69 |
-
model: str
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
class DeleteRatingsRequest(BaseModel):
|
| 73 |
-
"""طلب حذف مجموعة تقييمات محدّدة."""
|
| 74 |
-
keys: List[RatingKey]
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
# ------------------------------------------------------------
|
| 78 |
-
# 🤖 معلومات النماذج
|
| 79 |
-
# ------------------------------------------------------------
|
| 80 |
-
class ModelInfo(BaseModel):
|
| 81 |
-
"""معلومات نموذج تُعرض للواجهة (بدون كشف المسارات الداخلية)."""
|
| 82 |
-
model_index: int
|
| 83 |
-
model_key: str
|
| 84 |
-
display_name: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reverse-dictionary/backend/main.py
DELETED
|
@@ -1,66 +0,0 @@
|
|
| 1 |
-
# ============================================================
|
| 2 |
-
# 📄 الملف: main.py
|
| 3 |
-
# 🎯 الغرض: نقطة تشغيل الخادم (Backend) — تطبيق FastAPI.
|
| 4 |
-
#
|
| 5 |
-
# هذا هو الملف اللي تشغّله لرفع الـ backend. مسؤوليّاته:
|
| 6 |
-
# 1. إنشاء تطبيق FastAPI.
|
| 7 |
-
# 2. تفعيل CORS (يسمح للـ frontend باستدعاء الـ API).
|
| 8 |
-
# 3. ربط نقاط النهاية (من app/routes.py).
|
| 9 |
-
# 4. تحميل النماذج مسبقًا عند الإقلاع (warmup).
|
| 10 |
-
#
|
| 11 |
-
# طريقة التشغيل:
|
| 12 |
-
# cd backend
|
| 13 |
-
# uvicorn main:app --reload --port 8000
|
| 14 |
-
#
|
| 15 |
-
# توثيق الـ API يكون تلقائيًا على: http://localhost:8000/docs
|
| 16 |
-
# ============================================================
|
| 17 |
-
|
| 18 |
-
from contextlib import asynccontextmanager
|
| 19 |
-
|
| 20 |
-
from fastapi import FastAPI
|
| 21 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 22 |
-
|
| 23 |
-
from app.models_loader import resources
|
| 24 |
-
from app.routes import router
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
@asynccontextmanager
|
| 28 |
-
async def lifespan(app: FastAPI):
|
| 29 |
-
"""يُنفَّذ مرة عند إقلاع الخادم: تحميل النماذج والبيانات مسبقًا."""
|
| 30 |
-
print("🔥 تسخين الموارد (تحميل النماذج والـ embeddings)...")
|
| 31 |
-
try:
|
| 32 |
-
resources.warmup()
|
| 33 |
-
print("✅ الموارد جاهزة")
|
| 34 |
-
except FileNotFoundError as e:
|
| 35 |
-
# لا نوقف الخادم؛ فقط ننبّه المطوّر أن البيانات ناقصة
|
| 36 |
-
print(f"⚠️ تحذير: {e}")
|
| 37 |
-
yield
|
| 38 |
-
print("👋 إيقاف الخادم")
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
app = FastAPI(
|
| 42 |
-
title="القاموس العكسي للعربية — API",
|
| 43 |
-
description="واجهة برمجية لمقارنة 3 نماذج Embedding في البحث الدلالي العربي",
|
| 44 |
-
version="1.0.0",
|
| 45 |
-
lifespan=lifespan,
|
| 46 |
-
)
|
| 47 |
-
|
| 48 |
-
# ------------------------------------------------------------
|
| 49 |
-
# 🌐 CORS: نسمح للواجهة (تعمل على منفذ مختلف) باستدعاء الـ API
|
| 50 |
-
# ملاحظة: في الإنتاج، حدّد النطاقات المسموحة بدل "*"
|
| 51 |
-
# ------------------------------------------------------------
|
| 52 |
-
app.add_middleware(
|
| 53 |
-
CORSMiddleware,
|
| 54 |
-
allow_origins=["*"],
|
| 55 |
-
allow_methods=["*"],
|
| 56 |
-
allow_headers=["*"],
|
| 57 |
-
)
|
| 58 |
-
|
| 59 |
-
# ربط نقاط النهاية
|
| 60 |
-
app.include_router(router)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
@app.get("/")
|
| 64 |
-
def root():
|
| 65 |
-
"""نقطة فحص بسيطة للتأكّد أن الخادم يعمل."""
|
| 66 |
-
return {"status": "ok", "message": "الخادم يعمل ✅ — التوثيق على /docs"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reverse-dictionary/backend/requirements.txt
CHANGED
|
@@ -1,16 +1,13 @@
|
|
| 1 |
# ============================================================
|
| 2 |
# 📄 الملف: requirements.txt
|
| 3 |
-
# 🎯 الغرض: قائمة مكتبات بايثون اللازمة لتشغيل ال
|
| 4 |
# التثبيت: pip install -r requirements.txt
|
| 5 |
# ============================================================
|
| 6 |
|
| 7 |
-
|
| 8 |
-
uvicorn[standard] # خادم تشغيل FastAPI
|
| 9 |
-
pydantic # التحقّق من البيانات
|
| 10 |
python-dotenv # قراءة ملف .env
|
| 11 |
pandas # معالجة البيانات
|
| 12 |
numpy # العمليات الرقمية
|
| 13 |
scikit-learn # حساب تشابه الجيب التمام (cosine)
|
| 14 |
-
sentence-transformers # نماذج الـ embedding
|
| 15 |
torch # محرّك التعلّم العميق (يأتي مع sentence-transformers)
|
| 16 |
-
streamlit # واجهة Streamlit (frontend/streamlit_app.py)
|
|
|
|
| 1 |
# ============================================================
|
| 2 |
# 📄 الملف: requirements.txt
|
| 3 |
+
# 🎯 الغرض: قائمة مكتبات بايثون اللازمة لتشغيل المشروع (Streamlit).
|
| 4 |
# التثبيت: pip install -r requirements.txt
|
| 5 |
# ============================================================
|
| 6 |
|
| 7 |
+
streamlit # الواجهة (frontend/streamlit_app.py)
|
|
|
|
|
|
|
| 8 |
python-dotenv # قراءة ملف .env
|
| 9 |
pandas # معالجة البيانات
|
| 10 |
numpy # العمليات الرقمية
|
| 11 |
scikit-learn # حساب تشابه الجيب التمام (cosine)
|
| 12 |
+
sentence-transformers # نماذج الـ embedding + Cross-Encoder (reranker)
|
| 13 |
torch # محرّك التعلّم العميق (يأتي مع sentence-transformers)
|
|
|
reverse-dictionary/frontend/streamlit_app.py
CHANGED
|
@@ -146,11 +146,12 @@ def cached_search(query: str, model_key: str, rerank: bool):
|
|
| 146 |
# ============================================================
|
| 147 |
# 🖥️ الواجهة
|
| 148 |
# ============================================================
|
| 149 |
-
st.markdown("<h2 style='text-align:center;'>🔍 البحث الدلالي</h2>",
|
| 150 |
unsafe_allow_html=True)
|
| 151 |
st.markdown(
|
| 152 |
"<p style='text-align:center; color:#78909C;'>"
|
| 153 |
-
"اكتب تعريفًا، و
|
|
|
|
| 154 |
"</p>", unsafe_allow_html=True)
|
| 155 |
|
| 156 |
# زر بحث على اليسار + شريط الإدخال على اليمين
|
|
|
|
| 146 |
# ============================================================
|
| 147 |
# 🖥️ الواجهة
|
| 148 |
# ============================================================
|
| 149 |
+
st.markdown("<h2 style='text-align:center;'>🔍 تقييم البحث الدلالي</h2>",
|
| 150 |
unsafe_allow_html=True)
|
| 151 |
st.markdown(
|
| 152 |
"<p style='text-align:center; color:#78909C;'>"
|
| 153 |
+
"ساهِم في تقييم النماذج: اكتب تعريفًا، واطّلع على النتائج المقترحة من كل "
|
| 154 |
+
"نموذج، ثم اختر التقييم الأنسب لكل نموذج."
|
| 155 |
"</p>", unsafe_allow_html=True)
|
| 156 |
|
| 157 |
# زر بحث على اليسار + شريط الإدخال على اليمين
|