import os import json import gradio as gr from gradio_client import Client from huggingface_hub import HfApi, hf_hub_download import torch from transformers import AutoModelForCausalLM, AutoTokenizer # ════════════════════════════════════════════════════════════════════ # الإعدادات والروابط بين المساحات # ⚠️ تأكد من هذه الأسماء بالضبط — استُنتجت من رسالتك، صحّحها إذا اختلفت # ════════════════════════════════════════════════════════════════════ RADAR_SPACE_URL = "Asem75/Aiocr_Radar" MEDIA_SPACE_URL = "Asem75/My_teacher_enemation" # مساحة الصور والأنميشن (SD-Turbo + AnimateDiff) CONTROLLER_SPACE_URL = "Asem75/My_teacher_controller" # مساحة تهيئة الخزنة (للمرجعية فقط، التهيئة مُضمَّنة هنا مباشرة) VAULT_REPO_ID = "Asem75/aiocr_asistant" MODEL_ID = "Qwen/Qwen2.5-3B-Instruct" DEVICE = "cpu" RADAR_API_KEY = os.environ.get("INTERNAL_API_KEY", "").strip() HF_TOKEN = os.environ.get("HF_TOKEN", "").strip() if not HF_TOKEN: print("⚠️ تنبيه: HF_TOKEN غير مضبوط في أسرار هذه المساحة — القراءة/الكتابة في الخزنة قد تفشل إذا كانت خاصة.") _hf_api = HfApi(token=HF_TOKEN) if HF_TOKEN else HfApi() print("⏳ تحميل نموذج كوين (العقل المدبر)...") _tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) _model = AutoModelForCausalLM.from_pretrained( MODEL_ID, torch_dtype=torch.float32, device_map="cpu", low_cpu_mem_usage=True ) print("✅ كوين جاهز") # ════════════════════════════════════════════════════════════════════ # تهيئة ملفات الخزنة — نفس بنية كود "controller" الذي أرسلته، مُضمَّنة # هنا مباشرة (لا تعتمد على استدعاء مساحة قد تكون نائمة عند كل إقلاع). # # 🛠️ الإصلاح الذي وجدته في مراجعتي: رفع الملف البديل (عند عدم وجوده) # لم يكن محمياً بـ try/except خاص به — أي فشل في الرفع (توكن خاطئ/صلاحيات) # كان سيتسبب بانهيار المساحة بالكامل عند الإقلاع. الآن محمي ويُسجَّل # الفشل في السجل بدل إسقاط التطبيق. # ════════════════════════════════════════════════════════════════════ VAULT_STRUCTURE = { "curriculum_plans/lessons_division.json": { "description": "تقسيم الدروس والمواد التعليمية بين العلمي والأدبي والوسوم", "tracks": {"scientific": [], "literary": []} }, "curriculum_plans/co_planning_qwen.json": { "description": "ملف التخطيط المشترك مع موديل كوين لتحديد الأهداف التربوية", "qwen_guidelines": {}, "shared_plans": [] }, "learning_gaps/student_levels.json": { "description": "سجل مستويات الطلاب وتصنيف مهاراتهم التعليمية", "levels": {"beginner": [], "intermediate": [], "advanced": []} }, "learning_gaps/gap_analysis.json": { "description": "تحليل الفجوة التعليمية وخطط المعالجة الفردية للطلاب", "detected_gaps": [], "remedial_actions": [] }, "teacher_student_interaction/interaction_logs.json": { "description": "سجلات علاقة الطالب بالأستاذ والدعم التوجيهي", "teacher_notes": [], "student_feedback": [] }, } def initialize_vault_files(): log_lines = [] for file_path, default_content in VAULT_STRUCTURE.items(): try: hf_hub_download(repo_id=VAULT_REPO_ID, filename=file_path, repo_type="dataset", token=HF_TOKEN or None) log_lines.append(f"✅ {file_path} موجود مسبقاً وجاهز.") except Exception: try: local_filename = f"/tmp/{os.path.basename(file_path)}" with open(local_filename, "w", encoding="utf-8") as f: json.dump(default_content, f, ensure_ascii=False, indent=4) _hf_api.upload_file( path_or_fileobj=local_filename, path_in_repo=file_path, repo_id=VAULT_REPO_ID, repo_type="dataset", token=HF_TOKEN or None, ) os.remove(local_filename) log_lines.append(f"🚀 تم إنشاء ورفع: {file_path}") except Exception as e_upload: log_lines.append(f"❌ فشل إنشاء {file_path}: {e_upload}") return "\n".join(log_lines) print("⏳ فحص وتهيئة ملفات الخزنة...") VAULT_INIT_LOG = initialize_vault_files() print(VAULT_INIT_LOG) # ════════════════════════════════════════════════════════════════════ # المعلمون والأصوات (Tab 1 — حجز مبدئي، تطوير كامل لاحقاً) # ════════════════════════════════════════════════════════════════════ TEACHERS = [ ("الأستاذ عبود", "ar-JO-TaimNeural"), ("الأستاذة سلمى", "ar-EG-SalmaNeural"), ("الأستاذ حامد", "ar-SA-HamedNeural"), ] # ════════════════════════════════════════════════════════════════════ # الربط بمساحة الرادار (صوت) — بنفس نمط الحماية بمفتاح سري المُعتمد سابقاً # ════════════════════════════════════════════════════════════════════ def synth_via_radar(text, speaker_name): try: client = Client(RADAR_SPACE_URL) audio_url = client.predict(text, speaker_name, RADAR_API_KEY, api_name="/synth_arabic") if audio_url and isinstance(audio_url, (list, tuple)): audio_url = audio_url[0] return str(audio_url).strip() if audio_url else None except Exception as e: print(f"⚠️ خطأ الرادار: {e}") return None # ════════════════════════════════════════════════════════════════════ # الربط بمساحة الوسائط (صور/أنميشن) # ⚠️ ملاحظة أمان: نقطتا API هذه (generate_image, generate_animation) في # مساحة الوسائط غير محميتين بأي مفتاح سري حالياً (بخلاف الرادار). إذا # أردت حمايتهما بنفس مبدأ INTERNAL_API_KEY، أخبرني وأضيفه هناك أيضاً. # ════════════════════════════════════════════════════════════════════ def generate_image_via_media(prompt): try: client = Client(MEDIA_SPACE_URL) return client.predict(prompt, api_name="/generate_image") except Exception as e: print(f"⚠️ خطأ مساحة الوسائط (صورة): {e}") return None def generate_animation_via_media(prompt): try: client = Client(MEDIA_SPACE_URL) return client.predict(prompt, api_name="/generate_animation") except Exception as e: print(f"⚠️ خطأ مساحة الوسائط (أنميشن): {e}") return None # ════════════════════════════════════════════════════════════════════ # العقل المدبر (كوين) # ════════════════════════════════════════════════════════════════════ def ask_teacher(prompt, history): history = history or [] messages = [{ "role": "system", "content": "أنت كوين، العقل المدبر لمنصة تعليمية عربية. تساعد في صياغة دروس ومحتوى تعليمي قصير وواضح بالعربية، وتجيب بإيجاز ووضوح." }] messages.extend(history) messages.append({"role": "user", "content": prompt}) text_prompt = _tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = _tokenizer([text_prompt], return_tensors="pt").to(DEVICE) with torch.no_grad(): out_ids = _model.generate( **inputs, max_new_tokens=200, temperature=0.5, do_sample=True, repetition_penalty=1.15, pad_token_id=_tokenizer.eos_token_id ) out_ids = [o[len(i):] for i, o in zip(inputs.input_ids, out_ids)] response = _tokenizer.batch_decode(out_ids, skip_special_tokens=True)[0].strip() new_history = history + [{"role": "user", "content": prompt}, {"role": "assistant", "content": response}] return response, new_history def integration_test(prompt, teacher_choice, gen_image, gen_anim, history_state): if not prompt or not prompt.strip(): return "⚠️ اكتب نصاً أولاً", history_state, None, None, None response, new_history = ask_teacher(prompt, history_state) speaker_name = dict(TEACHERS).get(teacher_choice, "ar-JO-TaimNeural") audio_path = synth_via_radar(response, speaker_name) image_path = generate_image_via_media(response) if gen_image else None anim_path = generate_animation_via_media(response) if gen_anim else None return response, new_history, audio_path, image_path, anim_path # ════════════════════════════════════════════════════════════════════ # الواجهة: ستة تبويبات حجز مكان + لوحة فحص الربط # ════════════════════════════════════════════════════════════════════ with gr.Blocks(title="My Teacher — المنصة الرئيسية", theme=gr.themes.Soft()) as demo: gr.Markdown("# 🎓 المنصة التعليمية الذكية الموحدة") qwen_history_state = gr.State([]) with gr.Tabs(): with gr.Tab("👨‍🏫 إعدادات المعلم والصوت"): gr.Markdown( "🚧 **قيد التطوير.** سيتم هنا: تصفح المعلمين المتاحين، واختيار " "الأستاذ المناسب، وربط كل معلم بخامة صوتية عبر edge-tts/الرادار." ) with gr.Tab("🎬 الفيديوهات التعليمية المسجلة"): gr.Markdown("🚧 **قيد التطوير.** مكتبة الدروس والفيديوهات المسجلة مسبقاً للمراجعة.") with gr.Tab("🗂️ خطط الدروس والتقسيم"): gr.Markdown( "🚧 **قيد التطوير.** نافذة تفاعلية مع كوين للتخطيط المشترك، " "وتقسيم المواد بين العلمي والأدبي." ) with gr.Tab("📊 المستويات وتحليل الفجوة التعليمية"): gr.Markdown( "🚧 **قيد التطوير.** تتبع مستوى الطالب وتحليل الأخطاء " "(بالاستعانة بـ CAMeL-BERT) وخطط سد الفجوات." ) with gr.Tab("💬 علاقة الطالب والأستاذ"): gr.Markdown("🚧 **قيد التطوير.** الرسائل التوجيهية، التقييمات، والدعم الفردي.") with gr.Tab("🖼️ أرشيف وإدارة الوسائط"): gr.Markdown("🚧 **قيد التطوير.** فهرسة الصور والأنميشن المولّدة لمنع إعادة التوليد.") with gr.Tab("🔗 لوحة فحص الربط (API)"): gr.Markdown( "تجربة مباشرة تُثبت أن **كوين + الرادار (صوت) + مساحة الوسائط (صورة/أنميشن)** " "مربوطون فعلياً عبر API، قبل بناء التبويبات الست بالتفصيل." ) prompt_in = gr.Textbox(label="✍️ اكتب فكرة أو سؤالاً تعليمياً", lines=3) teacher_pick = gr.Dropdown(choices=[t[0] for t in TEACHERS], value=TEACHERS[0][0], label="🎙️ اختر المعلم/الصوت") with gr.Row(): want_image = gr.Checkbox(label="🖼️ توليد صورة توضيحية", value=False) want_anim = gr.Checkbox(label="🎬 توليد أنميشن (أبطأ بكثير)", value=False) run_btn = gr.Button("🚀 جرّب الربط الكامل", variant="primary") response_out = gr.Textbox(label="📝 رد كوين", lines=4, interactive=False) audio_out = gr.Audio(label="🔊 الصوت (عبر الرادار)", type="filepath", autoplay=True) with gr.Row(): image_out = gr.Image(label="🖼️ الصورة (عبر مساحة الوسائط)") anim_out = gr.Image(label="🎬 الأنميشن GIF (عبر مساحة الوسائط)") run_btn.click( fn=integration_test, inputs=[prompt_in, teacher_pick, want_image, want_anim, qwen_history_state], outputs=[response_out, qwen_history_state, audio_out, image_out, anim_out], api_name=False, ) with gr.Accordion("🗄️ سجل تهيئة الخزنة عند الإقلاع", open=False): gr.Textbox(value=VAULT_INIT_LOG, lines=8, interactive=False, show_label=False) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)