# app.py # ================== الاستيراد ================== import gradio as gr import pandas as pd import numpy as np import tempfile import os, pickle from datetime import datetime import io, base64 import arabic_reshaper from bidi.algorithm import get_display import matplotlib.pyplot as plt from sentence_transformers import SentenceTransformer, util from Bio import Entrez import gdown # --- إضافات خاصة بالـ Hugging Face dataset sync --- from huggingface_hub import HfApi, hf_hub_download, upload_file # ---------------------------------------------------------------- # ------------------ Data store & Google Drive sync (background sync) ------------------ import shutil, time, threading from datetime import datetime import os DATA_FOLDER = "data_store" DRIVE_FOLDER_ID = "1znvCrr63iSN2MWveAZvcXuGT3o5TfZc2" # Library_Backups folder ID on your Drive def ensure_data_folder(): os.makedirs(DATA_FOLDER, exist_ok=True) return DATA_FOLDER def get_data_path(filename): ensure_data_folder() return os.path.join(DATA_FOLDER, filename) # Attempt to import PyDrive2 and set up authentication functions. try: from pydrive2.auth import GoogleAuth from pydrive2.drive import GoogleDrive from oauth2client.service_account import ServiceAccountCredentials _GD_AVAILABLE = True except Exception: _GD_AVAILABLE = False def ensure_drive_auth(): """ Authenticate to Google Drive using a Service Account JSON file (client_secrets.json) placed inside data_store/. Works automatically on Spaces without manual login. """ if not _GD_AVAILABLE: print("❌ PyDrive2 not available.") return None try: service_account_path = os.path.join(DATA_FOLDER, "client_secrets.json") if not os.path.exists(service_account_path): print("⚠️ لم يتم العثور على ملف client_secrets.json في data_store/") return None # Create credentials from service account JSON scope = ['https://www.googleapis.com/auth/drive'] credentials = ServiceAccountCredentials.from_json_keyfile_name(service_account_path, scope) gauth = GoogleAuth() gauth.credentials = credentials drive = GoogleDrive(gauth) print("✅ تم الاتصال بـ Google Drive باستخدام Service Account بنجاح.") return drive except Exception as e: print("❌ فشل الاتصال بـ Google Drive:", e) return None def upload_to_drive(local_path, remote_name=None): """ Uploads a local file to Google Drive inside the folder defined by DRIVE_FOLDER_ID. """ try: drive = ensure_drive_auth() if drive is None: print("⚠️ لم يتم الاتصال بـ Google Drive، لم يتم رفع الملف:", local_path) return False if remote_name is None: remote_name = os.path.basename(local_path) q = f"'{DRIVE_FOLDER_ID}' in parents and title = '{remote_name}' and trashed=false" file_list = drive.ListFile({'q': q}).GetList() if file_list: f = file_list[0] f.SetContentFile(local_path) f.Upload() print(f"🔁 تم تحديث الملف الموجود في Google Drive: {remote_name}") else: file_metadata = {'title': remote_name, 'parents': [{'id': DRIVE_FOLDER_ID}]} f = drive.CreateFile(file_metadata) f.SetContentFile(local_path) f.Upload() print(f"✅ تم رفع ملف جديد إلى Google Drive: {remote_name}") return True except Exception as e: print("❌ خطأ أثناء رفع الملف إلى Google Drive:", e) return False def _background_sync(interval_seconds=30): """ Background thread to watch for new or modified CSV/XLSX files and upload them to Google Drive automatically. """ def _worker(): seen = {} while True: try: # scan for csv and xlsx files in project root for fname in os.listdir(): if fname.lower().endswith(('.csv', '.xlsx')) and fname not in (os.path.basename(__file__),): try: src_path = os.path.join(os.getcwd(), fname) dst_path = get_data_path(fname) # check modification time mtime = os.path.getmtime(src_path) if fname not in seen or seen[fname] < mtime: shutil.copy2(src_path, dst_path) seen[fname] = mtime success = upload_to_drive(dst_path, os.path.basename(dst_path)) if success: print(f"📤 تمت مزامنة الملف مع Google Drive: {fname} ({datetime.now().strftime('%Y-%m-%d %H:%M:%S')})") else: print(f"⚠️ فشل رفع الملف: {fname}") except Exception as e: print("❌ خطأ أثناء المزامنة:", e) except Exception: pass time.sleep(interval_seconds) t = threading.Thread(target=_worker, daemon=True) t.start() # تشغيل المزامنة التلقائية كل 20 ثانية try: _background_sync(interval_seconds=20) except Exception as e: print("⚠️ لم يتم تشغيل المزامنة التلقائية:", e) # ------------------ end background sync ------------------ # ================== HF dataset sync configuration ================== DATASET_REPO = "pharma-library/AI_LibExplorer" HF_TOKEN = os.getenv("HF_TOKEN") # يجب وضع هذا التوكن في Secrets باسم HF_TOKEN ADMIN_PASS = os.getenv("ADMIN_PASS", "pharmacy1") # ضع Secret باسم ADMIN_PASS بقيمة pharmacy1 # قائمة الملفات التي نريد التأكد من توافرها ومزامنتها LOCAL_FILES = [ "usage_stats.xlsx", "questions.csv", "suggestions.csv", "user_logs.csv", "faculty_borrow.csv", "bank_requests.csv" ] def download_data_files(): """Download listed files from dataset repo to local project root if present.""" for file_name in LOCAL_FILES: try: # hf_hub_download سيحفظ الملف في local_dir كما طلبنا hf_hub_download( repo_id=DATASET_REPO, filename=file_name, repo_type="dataset", local_dir=".", token=HF_TOKEN ) print(f"✅ تم تنزيل {file_name} من الـ Dataset.") except Exception as e: # الملف غير موجود بعد في الـ dataset — ليس خطأ، سيتم إنشاؤه عند الحاجة print(f"ℹ️ {file_name} غير موجود على الـ Dataset حالياً ({e}). سيُنشأ لاحقًا عند كتابة البيانات.") def upload_data_file(file_path): """Upload given file_path (relative path) to the dataset repo (overwrites/creates).""" try: # upload_file من huggingface_hub سيرفع الملف إلى المسار path_in_repo المحدد upload_file( path_or_fileobj=file_path, path_in_repo=os.path.basename(file_path), repo_id=DATASET_REPO, repo_type="dataset", token=HF_TOKEN ) print(f"✅ Uploaded {file_path} to {DATASET_REPO}") except Exception as e: print(f"⚠️ خطأ في رفع {file_path} إلى HF dataset: {e}") # تحميل الملفات عند بداية تشغيل التطبيق download_data_files() # ================== end HF dataset sync ================== # ================== دوال مساعدة ================== def download_from_drive(file_id, output): url = f"https://drive.google.com/uc?id={file_id}" try: gdown.download(url, output, quiet=True) except Exception: # إذا فشل التحميل من Drive نتابع لوجود ملفات محلية بالفعل pass # ================== إعدادات ومسارات ================== Entrez.email = "emananter0123@gmail.com" EMB_DIR = "embeddings_cache" os.makedirs(EMB_DIR, exist_ok=True) def embeddings_path(name): return os.path.join(EMB_DIR, f"{name}_embeddings.pkl") BOOKS_FILE, THESES_FILE, FAQ_FILE = "book.xlsx", "theses.xlsx", "faq.xlsx" QUESTIONS_FILE = "questions.csv" # ملف حفظ الأسئلة FACULTY_BORROW_FILE = "faculty_borrow.csv" # طلبات أعضاء هيئة التدريس للاستعارة # محاولة تنزيل الملفات من Google Drive (إن أردتِ يمكنك حذف السطور التالية إذا الملفات محلية) download_from_drive("1FElHiASfiVLeuHWYaqd2Q5foxWRlJT-O", BOOKS_FILE) # book.xlsx download_from_drive("1K2Mtze6ZdvfKUsFMCOWlRBjDq-ZnJNrv", THESES_FILE) # theses.xlsx download_from_drive("1ONjXtfv709BOxiIR0Qzmz6lQngnWEEso", FAQ_FILE) # faq.xlsx # ================== تحميل البيانات المحلية ================== def load_local_data(): if not os.path.exists(BOOKS_FILE) or not os.path.exists(THESES_FILE): raise FileNotFoundError("تأكدي من رفع الملفات book.xlsx و theses.xlsx في مجلد المشروع.") books = pd.read_excel(BOOKS_FILE).fillna("غير متوافر") theses = pd.read_excel(THESES_FILE).fillna("غير متوافر") # توحيد اسم عمود العنوان بين ملفات مختلفة if "Title" not in books.columns and "العنوان" in books.columns: books["Title"] = books["العنوان"].astype(str) elif "Title" not in books.columns: books["Title"] = books.iloc[:,0].astype(str) if "Title" not in theses.columns and "العنوان" in theses.columns: theses["Title"] = theses["العنوان"].astype(str) elif "Title" not in theses.columns: theses["Title"] = theses.iloc[:,0].astype(str) return books, theses books_df, theses_df = load_local_data() # ================== نموذج Semantic ================== MODEL_NAME = "all-MiniLM-L6-v2" model = SentenceTransformer(MODEL_NAME) def build_or_load_embeddings(df, name): path = embeddings_path(name) if os.path.exists(path): try: with open(path,"rb") as f: emb = pickle.load(f) if len(emb) == len(df): return emb except Exception: pass texts = df["Title"].astype(str).tolist() emb = model.encode(texts, convert_to_numpy=True, show_progress_bar=True) with open(path,"wb") as f: pickle.dump(emb,f) return emb books_embeddings = build_or_load_embeddings(books_df,"books") theses_embeddings = build_or_load_embeddings(theses_df,"theses") # ================== FAQ & مساعد ذكي ================== if os.path.exists(FAQ_FILE): faq_df = pd.read_excel(FAQ_FILE).fillna("") else: faq_df = pd.DataFrame({ "السؤال":["مواعيد المكتبة","خدمات المكتبة","كيفية الاستعارة","التسجيل في بنك المعرفة","التصوير","أقسام المكتبة","التواصل"], "الإجابة":[ "🕘 المكتبة تعمل من السبت إلى الخميس من 9 صباحًا حتى 2 ظهرًا", "📌 خدمات المكتبة: الإحاطة الجارية، التسجيل على بنك المعرفة، التصوير، البحث الإلكتروني، الاستعارة الخارجية", "✅ يتم استعارة الكتب لمدة أسبوعين (أو حسب القاعدة المحلية)", "🔗 التسجيل في بنك المعرفة عبر موقع بنك المعرفة المصري", "🖨️ التصوير متاح وفق القواعد", "📚 المكتبة بها: قاعة المراجع، قاعة الكتب الدراسية، قاعة الدوريات، قاعة الرسائل الجامعية", "☎️ للتواصل: صفحة الفيسبوك" ] }) faq_questions = faq_df["السؤال"].astype(str).tolist() faq_answers = faq_df["الإجابة"].astype(str).tolist() FAQ_EMB_PATH = embeddings_path("faq") def build_or_load_faq_embeddings(questions): if os.path.exists(FAQ_EMB_PATH): try: with open(FAQ_EMB_PATH,"rb") as f: emb = pickle.load(f) if len(emb) == len(questions): return emb except Exception: pass emb = model.encode(questions, convert_to_numpy=True, show_progress_bar=False) with open(FAQ_EMB_PATH,"wb") as f: pickle.dump(emb,f) return emb faq_embeddings = build_or_load_faq_embeddings(faq_questions) def library_assistant_smart(question): if not str(question).strip(): return "⚠️ من فضلك اكتب سؤالك" q_emb = model.encode([str(question)], convert_to_numpy=True) sims = util.cos_sim(q_emb, faq_embeddings)[0].cpu().numpy() idx_best = int(np.argmax(sims)) best_score = sims[idx_best] log_usage("المساعد الذكي", question, "FAQ", 1) if best_score < 0.75: return "❗ لم أجد إجابة مناسبة، حاول صياغة سؤالك بشكل مختلف." return f"الإجابة الأقرب:
{faq_answers[idx_best]}
درجة التشابه: {best_score:.2f}" # ================== CSS ================== CUSTOM_CSS = """ """ # ================== تتبع الاستخدام ================== def log_usage(tab, query="", mode="", results_count=0): file_path = "usage_stats.xlsx" entry = pd.DataFrame([{ "التاريخ": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "التب": tab, "الكلمة المفتاحية": query, "نوع البحث": mode, "عدد النتائج": results_count }]) if os.path.exists(file_path): try: old = pd.read_excel(file_path) entry = pd.concat([old, entry], ignore_index=True) except Exception: entry = entry entry.to_excel(file_path, index=False) # upload to HF dataset (auto backup) try: upload_data_file(file_path) except Exception as e: print("⚠️ خطأ أثناء رفع usage_stats:", e) # ================== عرض إحصاءات الاستخدام ================== def show_usage_stats(): file_path = "usage_stats.xlsx" if not os.path.exists(file_path): return "

📊 لا توجد بيانات بعد

" df = pd.read_excel(file_path) total = len(df) by_tab = df["التب"].value_counts() fig, ax = plt.subplots() labels = [get_display(arabic_reshaper.reshape(str(t))) for t in by_tab.index] ax.bar(labels, by_tab.values) for i, v in enumerate(by_tab.values): ax.text(i, v + 0.1, str(v), ha='center', va='bottom', fontsize=10) ax.set_title(get_display(arabic_reshaper.reshape("عدد مرات استخدام كل تب"))) ax.set_xlabel(get_display(arabic_reshaper.reshape("التب"))) ax.set_ylabel(get_display(arabic_reshaper.reshape("عدد العمليات"))) plt.xticks(rotation=30, ha='right') buf = io.BytesIO() plt.tight_layout() plt.savefig(buf, format="png") plt.close(fig) buf.seek(0) img = base64.b64encode(buf.read()).decode("utf-8") summary = f"

📈 إجمالي الاستخدام: {total} عملية

" html = summary + "" return CUSTOM_CSS + html + df.to_html(escape=False, index=False, classes="styled-table") # ================== تحويل نتائج إلى HTML أنيق ================== def results_to_html(df): if df.empty: return "

❌ لا توجد نتائج

" html = CUSTOM_CSS + "
" for i, row in df.iterrows(): html += "" html += f"" for col, val in row.items(): html += f"" html += "
📘 النتيجة {i+1}
{col}{val}
" html += "
" return html def df_to_html(df): if isinstance(df, str): return df if df.empty: return "

❌ لا توجد نتائج

" return CUSTOM_CSS + df.to_html(escape=False, index=False, classes="styled-table") # ================== حفظ النتائج إلى ملف Excel ================== def save_to_excel(df): if df is None or (isinstance(df, pd.DataFrame) and df.empty): tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") pd.DataFrame().to_excel(tmp.name, index=False) return tmp.name tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") if isinstance(df, pd.DataFrame): df.to_excel(tmp.name, index=False) else: pd.DataFrame({"result_html":[str(df)]}).to_excel(tmp.name, index=False) return tmp.name # ================== البحث المحلي ================== def local_search_df(query, category, mode): if not query or not str(query).strip(): return "

⚠️ اكتب كلمة أو جملة للبحث

", pd.DataFrame() if mode == "نصي": if category == "Books": if "العنوان" in books_df.columns: df = books_df[books_df["العنوان"].astype(str).str.contains(query, case=False, na=False)] else: df = books_df[books_df["Title"].astype(str).str.contains(query, case=False, na=False)] else: if "العنوان" in theses_df.columns: df = theses_df[theses_df["العنوان"].astype(str).str.contains(query, case=False, na=False)] else: df = theses_df[theses_df["Title"].astype(str).str.contains(query, case=False, na=False)] else: q_emb = model.encode([query], convert_to_numpy=True) if category == "Books": scores = util.cos_sim(q_emb, books_embeddings)[0].cpu().numpy() idx = np.argsort(-scores) df = books_df.iloc[idx] else: scores = util.cos_sim(q_emb, theses_embeddings)[0].cpu().numpy() idx = np.argsort(-scores) df = theses_df.iloc[idx] if df is None or df.empty: df = pd.DataFrame([{"نتيجة":"❌ لم يتم العثور على نتائج"}]) else: if "Title" in df.columns: df = df.drop(columns=["Title"]) log_usage("البحث المحلي", query, mode, len(df) if isinstance(df, pd.DataFrame) else 0) html_results = results_to_html(df) return html_results, df # ================== البحث في PubMed ================== def search_pubmed_html(query, max_results=5): if not query or not str(query).strip(): return "

⚠️ اكتب كلمة للبحث

" try: handle = Entrez.esearch(db="pubmed", term=query, retmax=max_results) record = Entrez.read(handle) handle.close() ids = record.get("IdList", []) if not ids: return "

❌ لا توجد نتائج

" handle = Entrez.efetch(db="pubmed", id=",".join(ids), rettype="xml", retmode="xml") records = Entrez.read(handle) handle.close() rows = [] for rec in records.get('PubmedArticle', []): try: title = rec['MedlineCitation']['Article']['ArticleTitle'] pmid = rec['MedlineCitation']['PMID'] link = f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/" rows.append({ "الموقع": "PubMed", "الوصف": "قاعدة بيانات PubMed", "العنوان": title, "الرابط": f"فتح" }) except Exception: continue if not rows: return "

❌ لم يتم العثور على نتائج صالحة.

" df = pd.DataFrame(rows) log_usage("المصادر الخارجية", query, "PubMed", len(df)) return CUSTOM_CSS + df.to_html(escape=False, index=False, classes="styled-table") except Exception as e: return f"

❌ حدث خطأ أثناء جلب البيانات من PubMed:
{str(e)}

" # ================== البحث في المواقع الخارجية ================== EXTERNAL_LINKS = { "Google Scholar": {"url": "https://scholar.google.com/scholar?q=", "desc": "محرك بحث للأبحاث"}, "PubMed": {"url": "https://pubmed.ncbi.nlm.nih.gov/?term=", "desc": "قاعدة بيانات PubMed"}, "Europe PMC": {"url": "https://europepmc.org/search?query=", "desc": "أبحاث Europe PMC"}, "ClinicalTrials.gov": {"url": "https://clinicaltrials.gov/search?term=", "desc": "تجارب سريرية"}, "OpenFDA": {"url": "https://open.fda.gov/", "desc": "بيانات الأدوية FDA"}, "MedlinePlus": {"url": "https://medlineplus.gov/search/?query=", "desc": "معلومات طبية"}, "DrugBank": {"url": "https://go.drugbank.com/", "desc": "قاعدة بيانات الأدوية"}, "WHO GHO": {"url": "https://www.who.int/data/gho/", "desc": "بيانات الصحة العالمية"}, "ICD-11": {"url": "https://icd.who.int/browse11/l-m/en", "desc": "التصنيف الدولي للأمراض"}, "ClinicalKey": {"url": "https://www.clinicalkey.com/search?q=", "desc": "مصادر طبية شاملة"}, "Scimago": {"url": "https://www.scimagojr.com/journalsearch.php?q=", "desc": "تصنيف الدوريات"}, "EKB": {"url": "https://www.ekb.eg/", "desc": "بنك المعرفة المصري"} } def external_search_html(query, site): try: if not query or not str(query).strip(): return "

⚠️ من فضلك اكتب كلمة للبحث

" if not site or site not in EXTERNAL_LINKS: return f"

⚠️ الموقع المحدد غير معروف أو غير موجود في القائمة.

" if site == "PubMed": return search_pubmed_html(query) base = EXTERNAL_LINKS[site]["url"] desc = EXTERNAL_LINKS[site]["desc"] if "?" in base or "=" in base: link = f"{base}{query}" else: link = base df = pd.DataFrame([ {"اسم الموقع": site, "الوصف": desc, "رابط البحث": f"للوصول إلى نتيجة البحث"} ]) log_usage("المصادر الخارجية", query, site, 1) html_table = df.to_html(escape=False, index=False, classes="styled-table") return CUSTOM_CSS + f"

🔗 نتائج البحث في {site}

" + html_table except Exception as e: return f"

❌ حدث خطأ أثناء تنفيذ البحث:
{str(e)}

" # ================== البحث في الدوريات ================== JOURNALS = { "Elsevier Journal Finder": {"url": "https://journalfinder.elsevier.com/", "desc": "اقتراح مجلات النشر"}, "Springer Journal Suggester": {"url": "https://journalsuggester.springer.com/", "desc": "اقتراح مجلات النشر"}, "DOAJ": {"url": "https://doaj.org/search/journals?ref=homepage-box&source=", "desc": "مجلات مفتوحة الوصول"}, "Journal Citation Reports": {"url": "https://jcr.clarivate.com/jcr/browse-journals?query=", "desc": "تقييم الدوريات العلمية"}, "Scimago": {"url": "https://www.scimagojr.com/journalsearch.php?q=", "desc": "تصنيف الدوريات"}, "Google Scholar": {"url": "https://scholar.google.com/scholar?q=", "desc": "بحث عام"} } def journal_search_html(query, site): try: if not query or not str(query).strip(): return "

⚠️ من فضلك اكتب كلمة للبحث

" if not site or site not in JOURNALS: return f"

⚠️ الموقع المحدد غير معروف أو غير موجود في قائمة الدوريات.

" base = JOURNALS[site]["url"] desc = JOURNALS[site]["desc"] if "?" in base or "=" in base: link = f"{base}{query}" else: link = base df = pd.DataFrame([{"اسم الموقع": site, "الوصف": desc, "رابط البحث": f"للوصول إلى نتيجة البحث"}]) log_usage("الدوريات", query, site, 1) html_table = df.to_html(escape=False, index=False, classes="styled-table") return CUSTOM_CSS + f"

📄 نتائج البحث في {site}

" + html_table except Exception as e: return f"

❌ حدث خطأ أثناء تنفيذ البحث:
{str(e)}

" # ================== أدوات الذكاء الاصطناعي ================== AI_TOOLS = { "Semantic Scholar": {"url": "https://www.semanticscholar.org/search?q=", "desc": "بحث أكاديمي يعتمد على الذكاء الاصطناعي"}, "Connected Papers": {"url": "https://www.connectedpapers.com/search?q=", "desc": "خرائط علاقات بين الأبحاث"}, "Research Rabbit": {"url": "https://www.researchrabbitapp.com/", "desc": "تتبع شبكات الأبحاث"}, "Elicit": {"url": "https://elicit.org/", "desc": "مساعد بحث علمي يستخدم AI"}, "Explainpaper": {"url": "https://www.explainpaper.com/", "desc": "تبسيط الأبحاث العلمية"}, "Consensus": {"url": "https://consensus.app/search/?q=", "desc": "إجابات سريعة مدعومة بالأدلة"} } def ai_search_html(query, site): try: if not query or not str(query).strip(): return "

⚠️ من فضلك اكتب كلمة للبحث

" if not site or site not in AI_TOOLS: return f"

⚠️ الأداة المحددة غير معروفة أو غير موجودة في القائمة.

" base = AI_TOOLS[site]["url"] desc = AI_TOOLS[site]["desc"] if "?" in base or "=" in base: link = f"{base}{query}" else: link = base df = pd.DataFrame([{"اسم الأداة": site, "الوصف": desc, "رابط البحث": f"للوصول إلى نتيجة البحث"}]) log_usage("أدوات الذكاء الاصطناعي", query, site, 1) html_table = df.to_html(escape=False, index=False, classes="styled-table") return CUSTOM_CSS + f"

🤖 نتائج البحث في {site}

" + html_table except Exception as e: return f"

❌ حدث خطأ أثناء تنفيذ البحث:
{str(e)}

" # ================== وظائف إدارة الأسئلة (CSV) ================== def ensure_questions_file(): if not os.path.exists(QUESTIONS_FILE): df = pd.DataFrame(columns=["id","name","question","answer","status","datetime"]) df.to_csv(QUESTIONS_FILE, index=False, encoding="utf-8-sig") def read_questions_df(): ensure_questions_file() try: df = pd.read_csv(QUESTIONS_FILE, encoding="utf-8-sig") except Exception: df = pd.DataFrame(columns=["id","name","question","answer","status","datetime"]) return df def save_question_to_csv(name, question): ensure_questions_file() df = read_questions_df() if df.empty: next_id = 1 else: try: next_id = int(df["id"].max()) + 1 except Exception: next_id = len(df) + 1 row = { "id": next_id, "name": name if name and str(name).strip() else "👤 طالب مجهول", "question": question, "answer": "", "status": "لم يتم الرد", "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } df = pd.concat([df, pd.DataFrame([row])], ignore_index=True) df.to_csv(QUESTIONS_FILE, index=False, encoding="utf-8-sig") # رفع تلقائي للملف بعد الحفظ try: upload_data_file(QUESTIONS_FILE) except Exception as e: print("⚠️ خطأ أثناء رفع questions.csv:", e) return next_id def save_answer_to_csv(qid, answer_text): ensure_questions_file() df = read_questions_df() if df.empty: return False, "لا توجد أسئلة." try: qid = int(qid) except Exception: return False, "معرّف السؤال غير صالح." if qid not in df["id"].values: return False, "لم يتم إيجاد السؤال بالمعرّف المحدد." df.loc[df["id"] == qid, "answer"] = answer_text df.loc[df["id"] == qid, "status"] = "تم الرد" df.to_csv(QUESTIONS_FILE, index=False, encoding="utf-8-sig") # رفع تلقائي بعد الحفظ try: upload_data_file(QUESTIONS_FILE) except Exception as e: print("⚠️ خطأ أثناء رفع questions.csv بعد الرد:", e) return True, "تم حفظ الرد بنجاح." def questions_to_html(df): if df is None or df.empty: return "

❌ لا توجد أسئلة حتى الآن.

" html = CUSTOM_CSS + "
" df_sorted = df.sort_values(by="id", ascending=False).reset_index(drop=True) for _, row in df_sorted.iterrows(): name = row.get("name","👤 طالب مجهول") qtext = row.get("question","") answer = row.get("answer","") status = row.get("status","لم يتم الرد") qid = row.get("id","") dt = row.get("datetime","") html += f"
# {qid} — {dt} — {name}
" html += f"
السؤال: {qtext}
" if status == "تم الرد" and str(answer).strip(): html += f"
الرد: {answer}
حالة: {status}
" else: html += f"
لم يتم الرد بعد
حالة: {status}
" html += "
" html += "
" return html # ================== وظائف إدارة طلبات الاستعارة (أعضاء هيئة التدريس) ================== def ensure_faculty_file(): if not os.path.exists(FACULTY_BORROW_FILE): df = pd.DataFrame(columns=["id","name","phone","nid","email","position","birthdate","status","datetime"]) df.to_csv(FACULTY_BORROW_FILE, index=False, encoding="utf-8-sig") def read_faculty_df(): ensure_faculty_file() try: df = pd.read_csv(FACULTY_BORROW_FILE, encoding="utf-8-sig") except Exception: df = pd.DataFrame(columns=["id","name","phone","nid","email","position","birthdate","status","datetime"]) return df def save_faculty_request(name, phone, nid, email, position, birthdate): ensure_faculty_file() df = read_faculty_df() if df.empty: next_id = 1 else: try: next_id = int(df["id"].max()) + 1 except Exception: next_id = len(df) + 1 row = { "id": next_id, "name": name, "phone": phone, "nid": nid, "email": email, "position": position, "birthdate": birthdate, "status": "قيد المراجعة", "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } df = pd.concat([df, pd.DataFrame([row])], ignore_index=True) df.to_csv(FACULTY_BORROW_FILE, index=False, encoding="utf-8-sig") # رفع تلقائي بعد الحفظ try: upload_data_file(FACULTY_BORROW_FILE) except Exception as e: print("⚠️ خطأ أثناء رفع faculty_borrow.csv:", e) return next_id # ================== واجهة Gradio ================== with gr.Blocks() as demo: with gr.Tab("📚 عن المكتبة"): with gr.Tabs(): with gr.Tab("🌟 رؤية المكتبة"): gr.Markdown("تسعى المكتبة إلى أن تكون مركزًا معرفيًا متميزًا يدعم البحث والتعليم ويُسهم في بناء مجتمع المعرفة.") with gr.Tab("🎯 رسالة المكتبة"): gr.Markdown("تقديم خدمات معلوماتية متكاملة تُمكّن الباحثين والطلاب من الوصول إلى مصادر المعرفة بسهولة وكفاءة.") with gr.Tab("🎯 أهداف المكتبة"): gr.Markdown("- دعم العملية التعليمية والبحثية.\n- تيسير الوصول إلى المعلومات والمصادر العلمية.\n- تشجيع استخدام التقنيات الحديثة في البحث.\n- تعزيز ثقافة القراءة والتعلم الذاتي.") with gr.Tab("🧩 الخدمات"): with gr.Tabs(): with gr.Tab("📘 الإحاطة الجارية"): gr.Markdown("خدمة تتيح للباحثين متابعة أحدث الإصدارات والموضوعات الجديدة في مجالات اهتمامهم.") with gr.Tab("🔗 التسجيل في بنك المعرفة"): gr.Markdown("يرجى ملء البيانات التالية للتسجيل في بنك المعرفة المصري:") name = gr.Textbox(label="الاسم بالكامل", placeholder="اكتب اسمك الثلاثي هنا") nid = gr.Textbox(label="رقم البطاقة", placeholder="14 رقمًا") birth = gr.Textbox(label="تاريخ الميلاد", placeholder="مثال: 1999-05-12") email = gr.Textbox(label="الإيميل الجامعي", placeholder="example@university.edu.eg") phone = gr.Textbox(label="رقم التليفون", placeholder="010XXXXXXXXX") submit_btn = gr.Button("إرسال الطلب") output_msg = gr.HTML() def save_registration(name, nid, birth, email, phone): import csv, os from datetime import datetime file_path = "bank_requests.csv" file_exists = os.path.exists(file_path) with open(file_path, mode="a", newline="", encoding="utf-8") as f: writer = csv.writer(f) if not file_exists: writer.writerow(["الاسم", "رقم البطاقة", "تاريخ الميلاد", "الإيميل الجامعي", "رقم التليفون", "تاريخ التسجيل"]) writer.writerow([name, nid, birth, email, phone, datetime.now().strftime("%Y-%m-%d %H:%M:%S")]) # رفع تلقائي للملف try: upload_data_file(file_path) except Exception as e: print("⚠️ خطأ أثناء رفع bank_requests.csv:", e) return "✅ تم استلام بياناتك بنجاح، يرجى مراجعة بريدك الجامعي خلال 24 ساعة." submit_btn.click(save_registration, inputs=[name, nid, birth, email, phone], outputs=output_msg) with gr.Tab("🖨️ التصوير"): gr.Markdown("توفر المكتبة خدمة تصوير المواد العلمية وفق القواعد المعمول بها داخل المكتبة.") with gr.Tab("🔍 البحث الإلكتروني"): gr.Markdown("يمكن للباحثين استخدام أجهزة المكتبة للبحث في قواعد البيانات الإلكترونية والمصادر العلمية.") with gr.Tab("📖 الاطلاع الداخلي"): gr.Markdown("تتيح المكتبة للرواد قراءة الكتب والمراجع داخل القاعات المخصصة دون الحاجة إلى استعارتها خارجًا.") # ------------------ تبويب الاستعارة الخارجية (معدّل) ------------------ with gr.Tab("📚 الاستعارة الخارجية"): gr.Markdown(""" تقدّم المكتبة خدمة الاستعارة الخارجية للكتب تبعًا لعدد النسخ، حيث أنه غير مسموح بالاستعارة الخارجية للكتب والمراجع ذات النسخة الواحدة أو ذات الطابع الخاص أو الدوريات أو الرسائل العلمية. """) with gr.Tabs(): with gr.Tab("📌 الاستعارة للطلاب"): gr.Markdown(""" 📌 يستخرج الطالب استمارة الاستعارة عن طريق ملء استمارة ضمان يقوم بختمها من شؤون الطلاب، مع ختمها بختم النسر الخاص بالجهة الحكومية التابع لها الضامن. """) gr.Markdown("### 📄 تحميل الاستمارات المطلوبة:") # روابط ملفات Google Drive student_form_url = "https://drive.google.com/uc?export=download&id=18BBlUYnKu37TEPqup8CeJGPv5UEOy1tY" guarantor_form_url = "https://drive.google.com/uc?export=download&id=1th9cJm0xxI16_YfLQ148l94zvkSqJmLD" gr.HTML(f"""
""") gr.Markdown("برجاء طباعة الاستمارتين وتوقيعهما قبل التوجه إلى المكتبة") with gr.Tab("📌 الاستعارة لأعضاء هيئة التدريس"): gr.Markdown("أعضاء هيئة التدريس يرجى تعبئة البيانات التالية لطلب الاستعارة:") fac_name = gr.Textbox(label="الاسم", placeholder="الاسم الكامل") fac_phone = gr.Textbox(label="رقم التليفون", placeholder="010XXXXXXXX") fac_nid = gr.Textbox(label="رقم البطاقة", placeholder="14 رقمًا") fac_email = gr.Textbox(label="الإيميل الجامعي", placeholder="example@university.edu.eg") fac_position = gr.Textbox(label="التوصيف الوظيفي", placeholder="مثال: أستاذ مساعد – قسم الصيدلة") fac_birth = gr.Textbox(label="تاريخ الميلاد", placeholder="مثال: 1980-05-12") fac_submit = gr.Button("إرسال الطلب") fac_msg = gr.Markdown("") def submit_faculty_request(name, phone, nid, email, position, birthdate): if not name or not str(name).strip(): return "الاسم مطلوب." if not phone or not str(phone).strip(): return "رقم التليفون مطلوب." if not nid or not str(nid).strip(): return "رقم البطاقة مطلوب." if not email or not str(email).strip(): return "الإيميل الجامعي مطلوب." # حفظ البيانات qid = save_faculty_request(name, phone, nid, email, position, birthdate) return f"✅ تم إرسال طلب الاستعارة (#{qid}) وسيتم مراجعته." fac_submit.click(submit_faculty_request, inputs=[fac_name, fac_phone, fac_nid, fac_email, fac_position, fac_birth], outputs=fac_msg) with gr.Tab("🔎 البحث المحلي"): query_local = gr.Textbox(label="اكتب كلمة البحث") category = gr.Radio(["Books", "Theses"], label="اختر الفئة") mode = gr.Radio(["نصي", "دلالي (Semantic)"], label="نوع البحث") btn_local = gr.Button("بحث") df_local_state = gr.State() output_local_html = gr.HTML() file_local = gr.File(label="⬇️ تحميل النتائج", visible=False) btn_local.click( local_search_df, inputs=[query_local, category, mode], outputs=[output_local_html, df_local_state] ) btn_save_local = gr.Button("📥 حفظ النتائج") btn_save_local.click( save_to_excel, inputs=df_local_state, outputs=file_local ) file_local.change(lambda x: gr.update(visible=True), inputs=file_local, outputs=file_local) with gr.Tab("🌐 المصادر الخارجية"): query_ext = gr.Textbox(label="كلمة البحث") site_ext = gr.Dropdown(list(EXTERNAL_LINKS.keys()), label="اختر الموقع") btn_ext = gr.Button("بحث") output_ext = gr.HTML() btn_ext.click(external_search_html, inputs=[query_ext, site_ext], outputs=output_ext) with gr.Tab("📄 الدوريات"): query_jour = gr.Textbox(label="كلمة البحث") site_jour = gr.Dropdown(list(JOURNALS.keys()), label="اختر الموقع") btn_jour = gr.Button("بحث") output_jour = gr.HTML() btn_jour.click(journal_search_html, inputs=[query_jour, site_jour], outputs=output_jour) with gr.Tab("🤖 أدوات الذكاء الاصطناعي"): query_ai = gr.Textbox(label="كلمة البحث") site_ai = gr.Dropdown(list(AI_TOOLS.keys()), label="اختر الأداة") btn_ai = gr.Button("بحث") output_ai = gr.HTML() btn_ai.click(ai_search_html, inputs=[query_ai, site_ai], outputs=output_ai) with gr.Tab("📑 برامج إدارة المراجع"): gr.Markdown("ℹ️ **تنويه:** هذه البرامج تحتاج تنزيل أو تثبيت الإضافات الخاصة بها.") gr.Markdown("### • **Mendeley** \n🔗 [زيارة الموقع](https://www.mendeley.com/)") gr.Markdown("### • **Zotero** \n🔗 [زيارة الموقع](https://www.zotero.org/)") gr.Markdown("### • **EndNote** \n🔗 [زيارة الموقع](https://endnote.com/)") with gr.Tab("💬 المساعد الذكي"): q_faq = gr.Textbox(label="اسأل مساعد المكتبة") ans_faq = gr.HTML() btn_faq = gr.Button("إجابة") btn_faq.click(library_assistant_smart, inputs=q_faq, outputs=ans_faq) with gr.Tab("📊 الإحصاءات"): stats_html = gr.HTML() btn_stats = gr.Button("عرض الإحصاءات") btn_stats.click(lambda: show_usage_stats(), outputs=stats_html) # -------------------- تبويب "📞 تواصل معنا" المضاف سابقًا -------------------- with gr.Tab("📞 تواصل معنا"): gr.Markdown(""" ## 📞 تواصل معنا يسعدنا تواصلك مع **مكتبة كلية الصيدلة – جامعة المنصورة** عبر القنوات التالية: 📍 **العنوان:** الدور الرابع – المبنى الإداري – كلية الصيدلة – جامعة المنصورة 📧 **البريد الإلكتروني:** [phrlib1@mans.edu.eg](mailto:phrlib1@mans.edu.eg) """) fb_link = "https://www.facebook.com/people/%D9%85%D9%83%D8%AA%D8%A8%D8%A9-%D9%83%D9%84%D9%8A%D8%A9-%D8%A7%D9%84%D8%B5%D9%8A%D8%AF%D9%84%D8%A9-%D8%AC%D8%A7%D9%85%D8%B9%D8%A9-%D8%A7%D9%84%D9%85%D9%86%D8%B5%D9%88%D8%B1%D8%A9/61554318849433/" youtube_link = "https://www.youtube.com/@%D9%85%D9%83%D8%AA%D8%A8%D8%A9%D9%83%D9%84%D9%8A%D8%A9%D8%A7%D9%84%D8%B5%D9%8A%D8%AF%D9%84%D8%A9%D8%AC%D8%A7%D9%85%D8%B9%D8%A9%D8%A7%D9%84%D9%85%D9%86%D8%B5%D9%88%D8%B1%D8%A9" email_link = "https://mail.google.com/mail/?view=cm&fs=1&to=phrlib1@mans.edu.eg" gr.Markdown("### 🌐 قنوات التواصل السريعة:") gr.HTML(f"""
""") # -------------------- تبويب "❓ اسأل أخصائي المكتبة" (التبويب الجديد للطلاب) -------------------- with gr.Tab("❓ اسأل أخصائي المكتبة"): gr.Markdown("### اسأل أخصائي المكتبة\nاكتب سؤالك وسيظهر في نظام المكتبة ليرد عليه الأخصائي لاحقًا.") student_name = gr.Textbox(label="الاسم (اختياري)", placeholder="يمكن تركه فارغًا") student_question = gr.Textbox(label="السؤال", lines=4, placeholder="اكتب سؤالك هنا...", interactive=True) student_send = gr.Button("إرسال السؤال") student_result = gr.Markdown("") student_questions_html = gr.HTML() def load_questions_html(): df = read_questions_df() return questions_to_html(df) def student_submit_question(name, question): if not question or not str(question).strip(): return "⚠️ اكتب سؤالك قبل الإرسال.", questions_to_html(read_questions_df()) qid = save_question_to_csv(name, question) df = read_questions_df() return f"✅ تم إرسال سؤالك (#{qid})، سنقوم بالرد في أقرب وقت ممكن.", questions_to_html(df) student_send.click(student_submit_question, inputs=[student_name, student_question], outputs=[student_result, student_questions_html]) btn_load_questions = gr.Button("تحديث عرض الأسئلة") btn_load_questions.click(lambda: load_questions_html(), outputs=student_questions_html) # -------------------- 📂 تبويب إدارة خدمات المكتبة -------------------- with gr.Tab("📂 إدارة خدمات المكتبة"): gr.Markdown("### 🔐 تسجيل الدخول لإدارة خدمات المكتبة") password = gr.Textbox(label="كلمة المرور", type="password", placeholder="أدخل كلمة المرور هنا") login_btn = gr.Button("دخول") login_msg = gr.Markdown("") admin_area = gr.Group(visible=False) with admin_area: gr.Markdown("### عرض طلبات التسجيل في بنك المعرفة") refresh_btn = gr.Button("🔄 تحديث البيانات") data_table = gr.Dataframe(headers=["الاسم", "رقم البطاقة", "تاريخ الميلاد", "الإيميل الجامعي", "رقم التليفون", "تاريخ التسجيل"], interactive=False) download_btn = gr.Button("⬇️ تنزيل الطلبات كملف Excel") download_file = gr.File(label="ملف Excel", visible=False) import pandas as pd, os, tempfile def check_password(pw): # اقرأ كلمة مرور الأدمن من Secret (ADMIN_PASS) — لو مش موجود يستخدم 'pharmacy1' كقيمة افتراضية قصيرة الأمد correct_pw = os.getenv("ADMIN_PASS", "pharmacy1") if pw == correct_pw: return gr.update(visible=True), "✅ تم تسجيل الدخول بنجاح." else: return gr.update(visible=False), "❌ كلمة المرور غير صحيحة." login_btn.click(check_password, inputs=password, outputs=[admin_area, login_msg]) def load_requests(): file_path = "bank_requests.csv" if not os.path.exists(file_path): return pd.DataFrame(columns=["الاسم", "رقم البطاقة", "تاريخ الميلاد", "الإيميل الجامعي", "رقم التليفون", "تاريخ التسجيل"]) df = pd.read_csv(file_path) return df def export_excel(df): if df is None or df.empty: return gr.update(visible=False) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") df.to_excel(tmp.name, index=False) return gr.update(value=tmp.name, visible=True) refresh_btn.click(load_requests, outputs=data_table) download_btn.click(export_excel, inputs=data_table, outputs=download_file) # -------------------- قسم إدارة الأسئلة داخل منطقة الإدارة -------------------- with admin_area: gr.Markdown("### 🛠️ إدارة الأسئلة") admin_refresh_q = gr.Button("🔄 تحديث الأسئلة") admin_questions_df = gr.Dataframe(headers=["id","name","question","answer","status","datetime"], interactive=False) admin_select_q = gr.Dropdown(choices=[], label="اختر رقم السؤال للرد عليه (ID)", value=None) admin_answer_box = gr.Textbox(label="اكتب الرد هنا", lines=3, placeholder="أدخل رد الأخصائي...") admin_save_answer_btn = gr.Button("حفظ الرد") admin_msg = gr.Markdown("") admin_export_btn = gr.Button("⬇️ تنزيل الأسئلة كملف Excel") admin_export_file = gr.File(label="ملف الأسئلة", visible=False) def admin_load_questions(): df = read_questions_df() if df is None or df.empty: df_show = pd.DataFrame(columns=["id","name","question","answer","status","datetime"]) else: df_show = df.sort_values(by="id", ascending=False) choices = df_show["id"].astype(str).tolist() if not df_show.empty else [] return df_show, gr.update(choices=choices, value=choices[0] if choices else None) def admin_save_answer(qid, answer_text): success, msg = save_answer_to_csv(qid, answer_text) if not success: return "❌ " + msg, admin_load_questions()[0] df_show, dd_update = admin_load_questions() return "✅ تم حفظ الرد.", df_show def admin_export_questions(): df = read_questions_df() if df is None or df.empty: return gr.update(visible=False) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") df.to_excel(tmp.name, index=False) return gr.update(value=tmp.name, visible=True) admin_refresh_q.click(lambda: admin_load_questions()[0], outputs=admin_questions_df) admin_refresh_q.click(lambda: admin_load_questions()[1], outputs=admin_select_q) admin_save_answer_btn.click(admin_save_answer, inputs=[admin_select_q, admin_answer_box], outputs=[admin_msg, admin_questions_df]) admin_export_btn.click(admin_export_questions, outputs=admin_export_file) # -------------------- قسم إدارة طلبات الاستعارة لأعضاء هيئة التدريس -------------------- with admin_area: gr.Markdown("### 🗂️ إدارة طلبات الاستعارة (أعضاء هيئة التدريس)") admin_refresh_fac = gr.Button("🔄 تحديث طلبات الاستعارة") admin_fac_df = gr.Dataframe(headers=["id","name","phone","nid","email","position","birthdate","status","datetime"], interactive=False) admin_export_fac_btn = gr.Button("⬇️ تنزيل طلبات الاستعارة كملف Excel") admin_export_fac_file = gr.File(label="ملف طلبات الاستعارة", visible=False) def admin_load_faculty(): df = read_faculty_df() if df is None or df.empty: df_show = pd.DataFrame(columns=["id","name","phone","nid","email","position","birthdate","status","datetime"]) else: df_show = df.sort_values(by="id", ascending=False) return df_show def admin_export_faculty(): df = read_faculty_df() if df is None or df.empty: return gr.update(visible=False) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") df.to_excel(tmp.name, index=False) return gr.update(value=tmp.name, visible=True) admin_refresh_fac.click(lambda: admin_load_faculty(), outputs=admin_fac_df) admin_export_fac_btn.click(admin_export_faculty, outputs=admin_export_fac_file) gr.Markdown("---\n📌 *المشروع أعدته eman anter*") # تشغيل التطبيق if __name__ == "__main__": demo.launch(share=True)