Spaces:
Sleeping
Sleeping
| import os | |
| import re | |
| import json | |
| import time | |
| import gc | |
| import warnings | |
| import asyncio | |
| import threading | |
| import edge_tts | |
| import gradio as gr | |
| from gradio_client import Client | |
| from huggingface_hub import HfApi, hf_hub_download | |
| import torch | |
| from PIL import Image | |
| import sympy as sp | |
| from transformers import AutoModelForCausalLM, AutoTokenizer, TrOCRProcessor, VisionEncoderDecoderModel | |
| warnings.filterwarnings("ignore") | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงูุฅุนุฏุงุฏุงุช ูุงูุฑูุงุจุท ุจูู ุงูู ุณุงุญุงุช | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| CONTROLLER_SPACE_URL = "Asem75/My_teacher_controller" | |
| MEDIA_SPACE_URL = "Asem75/My_teacher_enemation" | |
| VAULT_REPO_ID = "Asem75/aiocr_asistant" | |
| MODEL_ID = "Qwen/Qwen2.5-3B-Instruct" | |
| WHISPER_MODEL = "openai/whisper-medium" | |
| OCR_MODEL_ID = "RayR1/trocr-base-arabic-handwritten" # ุชุตุญูุญ: ุฑุงุฌุน ู ูู Chat ููุชูุตูู | |
| DEVICE = "cpu" | |
| MAX_LESSON_CHARS = 1200 | |
| 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() | |
| # โ ๏ธ ุชุญู ูู ูุณูู ููููู ุจูุงุกู ุนูู ุทูุจู: ูุง ููุญู ููู ุนูุฏ ุฅููุงุน ุงูุณูุฑูุฑุ ุจู | |
| # ููุท ุนูุฏ ุฃูู ุงุณุชุฎุฏุงู ูุนูู (ุฃูู ุฏุฎูู ูุบุฑูุฉ ุงูุตู ูุฅุฑุณุงู ุณุคุงู) โ ูุชุฎููู | |
| # ุงูุถุบุท ุนูู ุงูู ุณุงุญุฉ ุนูุฏ ุงูุฅููุงุน ูุนูุฏ ุงูุชุตูุญ ุจุฏูู ู ุญุงุฏุซุฉ ูุนููุฉ. | |
| _llm_tokenizer, _llm_model = None, None | |
| _qwen_lock = threading.Lock() | |
| def load_qwen_lazy(): | |
| global _llm_tokenizer, _llm_model | |
| if _llm_model is not None: | |
| return _llm_tokenizer, _llm_model | |
| with _qwen_lock: | |
| if _llm_model is not None: | |
| return _llm_tokenizer, _llm_model | |
| print("โณ ุชุญู ูู ูู ูุฐุฌ ูููู (ุฃูู ุงุณุชุฎุฏุงู ูุนูู)...") | |
| _llm_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) | |
| _llm_model = AutoModelForCausalLM.from_pretrained( | |
| MODEL_ID, torch_dtype=torch.float32, device_map="cpu", low_cpu_mem_usage=True | |
| ) | |
| print("โ ูููู ุฌุงูุฒ") | |
| return _llm_tokenizer, _llm_model | |
| # ูุงุฆู ุฉ ุฃุตูุงุช ู ุงููุฑูุณููุช ุงูุนุฑุจูุฉ ุงูู ูุซูููุฉ (ููุณ ุงููุงุฆู ุฉ ุงูู ุนุชู ุฏุฉ ูู ุงูุฑุงุฏุงุฑ) | |
| MICROSOFT_VOICES = [ | |
| "ar-EG-SalmaNeural", "ar-EG-ShakirNeural", "ar-JO-SanaNeural", "ar-JO-TaimNeural", | |
| "ar-SA-ZariyahNeural", "ar-SA-HamedNeural", "ar-AE-FatimaNeural", "ar-AE-HamdanNeural", | |
| "ar-IQ-RanaNeural", "ar-IQ-BasselNeural", "ar-LB-LaylaNeural", "ar-LB-RamiNeural", | |
| "ar-SY-AmanyNeural", "ar-SY-LaithNeural", "ar-MA-MounaNeural", "ar-MA-JamalNeural", | |
| "ar-TN-ReemNeural", "ar-TN-HediNeural", "ar-DZ-AminaNeural", "ar-DZ-IsmaelNeural", | |
| "ar-LY-ImanNeural", "ar-LY-OmarNeural", "ar-KW-NouraNeural", "ar-KW-FahedNeural", | |
| "ar-QA-AmalNeural", "ar-QA-MoazNeural", "ar-OM-AyshaNeural", "ar-OM-AbdullahNeural", | |
| "ar-BH-LailaNeural", "ar-BH-AliNeural", "ar-YE-MaryamNeural", "ar-YE-SalehNeural", | |
| ] | |
| VOICE_ROLES = ["ู ุญุงูุฑ", "ุดุฑุญ", "ุฃุณุฆูุฉ"] | |
| DEFAULT_TEACHER_PREFS = [ | |
| {"name": "ุงูู ุนูู ุงูุฃูู", "ู ุญุงูุฑ": "ar-JO-TaimNeural", "ุดุฑุญ": "ar-SA-HamedNeural", "ุฃุณุฆูุฉ": "ar-EG-ShakirNeural"}, | |
| {"name": "ุงูู ุนูู ุฉ ุงูุซุงููุฉ", "ู ุญุงูุฑ": "ar-EG-SalmaNeural", "ุดุฑุญ": "ar-AE-FatimaNeural", "ุฃุณุฆูุฉ": "ar-LB-LaylaNeural"}, | |
| {"name": "ุงูู ุนูู ุงูุซุงูุซ", "ู ุญุงูุฑ": "ar-SA-HamedNeural", "ุดุฑุญ": "ar-JO-TaimNeural", "ุฃุณุฆูุฉ": "ar-IQ-BasselNeural"}, | |
| ] | |
| THEME_CSS = { | |
| "๐ ููุงุฑู": "", | |
| "๐ ูููู": "body, .gradio-container { background-color:#0b0f19 !important; color:#f3f4f6 !important; } .gr-button { background:#1e293b !important; color:#f3f4f6 !important; }", | |
| "๐จ ู ูููู": "body, .gradio-container { background: linear-gradient(135deg,#fef3c7,#dbeafe) !important; } .gr-button { background: linear-gradient(135deg,#f59e0b,#3b82f6) !important; color:white !important; }", | |
| } | |
| def apply_theme(theme_name): | |
| css = THEME_CSS.get(theme_name, "") | |
| return f"<style>{css}</style>" | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ๐ ุงูุฑุจุท ุจู Controller (Phi-4-mini โ ุฃุณุชุงุฐ ุนูู ูุ ูุต ููุทุ ูุง ุตูุช ุฅุทูุงูุงู) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def ask_science_teacher_via_controller(text, task_type="teach", history=None): | |
| try: | |
| client = Client(CONTROLLER_SPACE_URL) | |
| return client.predict(text, task_type, history or [], api_name="/ask_science_teacher") | |
| except Exception as e: | |
| print(f"โ ๏ธ ุฎุทุฃ ุงูููุชุฑููุฑ: {e}") | |
| return None | |
| def deliver_via_qwen(original_question, phi_content, history): | |
| """Phi ูุง ูุชุญุฏุซ ู ุน ุงูุทุงูุจ ู ุจุงุดุฑุฉ ุฃุจุฏุงู โ ูู ุฑ ุนุจุฑ ูููู ุฏุงุฆู ุงู. | |
| โ ๏ธ ูุจูู ุงูุณุฌู ูุฏููุงู ุจุณุคุงู ุงูุทุงูุจ ุงูุญูููู (ูุง ูุต ุงูุชูุฌูู ุงูุฏุงุฎูู).""" | |
| delivery_prompt = ( | |
| f"ุฒู ููู ุงูู ุนูู ุงูู ุชุฎุตุต ุจุงูุนููู (Phi) ุฃุนุทุงู ูุฐุง ุงูู ุญุชูู ูุณุคุงู ุงูุทุงูุจ \"{original_question}\":\n" | |
| f"{phi_content}\n\nุฃุนุฏ ุตูุงุบุชู ุจุฃุณููุจู ุงููุฏูุฏ ู ุจุงุดุฑุฉ ููุทุงูุจ (30 ููู ุฉ ูุญุฏ ุฃูุตูุ ุฎุทูุงุชุ ุณุคุงู ุฎุชุงู ู)." | |
| ) | |
| response, _ = ask_teacher(delivery_prompt, history) | |
| new_history = history + [{"role": "user", "content": original_question}, {"role": "assistant", "content": response}] | |
| return response, new_history | |
| def ask_teacher(prompt, history): | |
| tokenizer, model = load_qwen_lazy() | |
| 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() | |
| gc.collect() # ุชุฎููู ุฐุฑูุฉ ุงูุฐุงูุฑุฉ ุจุนุฏ ูู ุชูููุฏ | |
| new_history = history + [{"role": "user", "content": prompt}, {"role": "assistant", "content": response}] | |
| return response, new_history | |
| def generate_image_via_media(prompt): | |
| try: | |
| client = Client(MEDIA_SPACE_URL) | |
| return client.predict(prompt, RADAR_API_KEY, api_name="/generate_image") | |
| except Exception as e: | |
| print(f"โ ๏ธ ุฎุทุฃ ู ุณุงุญุฉ ุงููุณุงุฆุท: {e}") | |
| return None | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ๐๏ธ ุฎุท ู ุนุงูุฌุฉ ุงูุตูุช ุงูู ุชูุฏู (4 ู ุฑุงุญู) โ ูู ู ุฑุญูุฉ ู ุญู ูุฉ ุจุดูู ู ุณุชูู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| SKIP_HEAVY_AUDIO_FILTERS = os.environ.get("SKIP_HEAVY_AUDIO_FILTERS", "").strip().lower() in ("1", "true", "yes") | |
| if SKIP_HEAVY_AUDIO_FILTERS: | |
| print("๐งช ูุถุน ุงูุชุดุฎูุต: ุชุฎุทูู DeepFilterNet ูMetricGAN+ (ู ูุนูู ุนุจุฑ SKIP_HEAVY_AUDIO_FILTERS)") | |
| def normalize_audio_pydub(input_path): | |
| try: | |
| from pydub import AudioSegment, effects | |
| sound = AudioSegment.from_file(input_path) | |
| normalized = effects.normalize(sound) | |
| output_path = "/tmp/step1_normalized.wav" | |
| normalized.export(output_path, format="wav") | |
| return output_path | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดูุช ุฎุทูุฉ ุชุทุจูุน ุงูุตูุช: {e}") | |
| return input_path | |
| _df_model, _df_state = None, None | |
| def denoise_with_deepfilternet(input_path): | |
| global _df_model, _df_state | |
| try: | |
| from df.enhance import enhance as df_enhance, init_df, load_audio as df_load_audio, save_audio as df_save_audio | |
| if _df_model is None: | |
| print("โณ ุชุญู ูู DeepFilterNet...") | |
| _df_model, _df_state, _ = init_df() | |
| audio, _ = df_load_audio(input_path, sr=_df_state.sr()) | |
| enhanced = df_enhance(_df_model, _df_state, audio) | |
| output_path = "/tmp/step2_denoised.wav" | |
| df_save_audio(output_path, enhanced, _df_state.sr()) | |
| return output_path | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดูุช ุฎุทูุฉ ุชุตููุฉ ุงูุถูุถุงุก: {e}") | |
| return input_path | |
| _metricgan_model = None | |
| def isolate_speaker_metricgan(input_path): | |
| global _metricgan_model | |
| try: | |
| import torchaudio | |
| from speechbrain.inference.enhancement import SpectralMaskEnhancement | |
| if _metricgan_model is None: | |
| print("โณ ุชุญู ูู MetricGAN+...") | |
| _metricgan_model = SpectralMaskEnhancement.from_hparams( | |
| source="speechbrain/metricgan-plus-voicebank", savedir="/tmp/pretrained_metricgan", | |
| ) | |
| noisy = _metricgan_model.load_audio(input_path).unsqueeze(0) | |
| enhanced = _metricgan_model.enhance_batch(noisy, lengths=torch.tensor([1.0])) | |
| output_path = "/tmp/step3_isolated.wav" | |
| torchaudio.save(output_path, enhanced.cpu(), 16000) | |
| return output_path | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดูุช ุฎุทูุฉ ุนุฒู ุงูู ุชุญุฏุซ: {e}") | |
| return input_path | |
| def preprocess_student_audio(raw_audio_path): | |
| t0 = time.time() | |
| step1 = normalize_audio_pydub(raw_audio_path) | |
| print(f"โฑ๏ธ ุชุทุจูุน ุงูุตูุช (pydub): {time.time() - t0:.1f}s") | |
| if SKIP_HEAVY_AUDIO_FILTERS: | |
| return step1 | |
| # โ ๏ธ ุจูุงุกู ุนูู ุทูุจู: ุชุฎุทูู DeepFilterNet ููุงุฆูุงู ู ู ุงูู ุณุงุฑ ุงููุนูู ุญุงููุงู | |
| # (ุงูุฏุงูุฉ denoise_with_deepfilternet ุชุจูู ูู ุงูููุฏ ูุงุณุชุฎุฏุงู ูุง ูุงุญูุงู | |
| # ูู ุงุญุชุฌุชูุง ู ุณุชูุจูุงูุ ููุท ูุง ุชูุณุชุฏุนู ุงูุขู) โ ููุท ููุชุฑ ุงูุนุฒู (MetricGAN+). | |
| t2 = time.time() | |
| step3 = isolate_speaker_metricgan(step1) | |
| print(f"โฑ๏ธ ุนุฒู ุงูู ุชุญุฏุซ (MetricGAN+): {time.time() - t2:.1f}s") | |
| return step3 | |
| _whisper_pipe = None | |
| def load_whisper(): | |
| global _whisper_pipe | |
| if _whisper_pipe is not None: | |
| return _whisper_pipe | |
| from transformers import pipeline | |
| print("โณ ุชุญู ูู Whisper-medium...") | |
| _whisper_pipe = pipeline("automatic-speech-recognition", model=WHISPER_MODEL, device=DEVICE, chunk_length_s=30) | |
| return _whisper_pipe | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงูุฑุงุฏุงุฑ โ ุงูุตูุช ููุท ู ู ููุงุ ุฏุงุฆู ุงู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ๐งช Edge-TTS ู ุญูู ู ุจุงุดุฑ ุฏุงุฎู ูุฐู ุงูู ุณุงุญุฉ โ ุจุฏู ุงูุงุชุตุงู ุจุงูุฑุงุฏุงุฑุ ูุชุฌุฑุจุฉ | |
| # ูุชุดุฎูุต/ุญู ู ุดููุฉ ุงูุชุนููู ุงูุทููู (ูุงูุช ุชุธูุฑ "processing 186s" ุจุฏูู ูุชูุฌุฉ). | |
| # ๐ ุงูุฑุงุฏุงุฑ ุญูุฐู ููุงุฆูุงู ู ู ูุฐุง ุงูุชุทุจูู ุจูุงุกู ุนูู ุทูุจู โ ูุง ููุฌุฏ ุฃู | |
| # ุงุชุตุงู ุจู ุนูู ุงูุฅุทูุงู ุจุนุฏ ุงูุขู. ุงูุตูุช 100% ู ู Edge-TTS ุงูู ุญูู ููุท. | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ๐ง ุฅุตูุงุญ ุฌุฐุฑู: ุญููุฉ asyncio ุงูุฏุงุฆู ุฉ ูู ุฎูุท ู ููุตู (_start_edge_loop) | |
| # ูุงูุช ุงูุณุจุจ ุงููุนูู ูุงูู ุณุชู ุฑ ูุฎุทุฃ "ValueError: Invalid file descriptor: -1" | |
| # โ ููุฏ ุชุฃูุฏูุง ุฃูู ุธูุฑ ุญุชู ูู ุงูู ุณุงุญุฉ ุงููุฏูู ุฉ (ุงูุฑุงุฏุงุฑ) ุจููุณ ุงููู ุทุ ููู | |
| # ุนูุจ ูู ุงููู ุท ููุณู ูุง ูู ู ุณุงุญุฉ ู ุนูููุฉ. ุงูุญู: asyncio.run() ููู ุงุณุชุฏุนุงุก โ | |
| # ุชูุดุฆ ุญููุฉ ุฌุฏูุฏุฉ ูุชููุธูููุง ุจุงููุงู ู (ุฅูุบุงุก ุงูู ูุงู + ุฅุบูุงู async generators | |
| # + ุฅุบูุงู ุงูุญููุฉ) ูู ูู ู ุฑุฉุ ููุง ูุจูู ุฃู ูุงุฆู ุญููุฉ ู ุนููู ููุณุจูุจ ูุฐุง ุงูุฎุทุฃ | |
| # ุนูุฏ ุฌู ุน ุงููู ุงู ุฉ ูุงุญูุงู. | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def call_edge_tts_local(text, voice): | |
| filepath = f"/tmp/edge_{int(time.time() * 1000)}.mp3" | |
| async def _task(): | |
| communicate = edge_tts.Communicate(text.strip(), voice) | |
| await communicate.save(filepath) | |
| try: | |
| asyncio.run(_task()) | |
| return filepath | |
| except Exception as e: | |
| print(f"โ ๏ธ ุฎุทุฃ Edge-TTS ุงูู ุญูู: {e}") | |
| return None | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงูุฎุฒูุฉ: ุงูุฏุฑูุณ + ู ูู ุงูุทุงูุจ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def clean_subject_for_path(subject): | |
| """ูุญุฐู ุงูุฅูู ูุฌู ุงูุจุงุฏุฆ ู ู ุงุณู ุงูู ุงุฏุฉ (๐งฎ ุงูุฑูุงุถูุงุช โ ุงูุฑูุงุถูุงุช) ูู ุทุงุจูุฉ | |
| ุฃุณู ุงุก ุงูู ุฌูุฏุงุช ุงูุญููููุฉ ูู ุงูุฎุฒูุฉุ ุงูุชู ูุง ุชุญุชูู ุฅูู ูุฌู.""" | |
| if not subject: | |
| return subject | |
| parts = subject.strip().split(" ", 1) | |
| return parts[-1].strip() if len(parts) > 1 else subject.strip() | |
| def list_lessons_in_vault(grade, semester, subject): | |
| try: | |
| # โ ๏ธ ุงูุชุฑุชูุจ ุงูุตุญูุญ ุงูู ุคูููุฏ ู ู ุงูุฎุฒูุฉ ุงููุนููุฉ: ุงูู ุงุฏุฉ/ุงููุตู/ุงูุตู | |
| clean_subject = clean_subject_for_path(subject) | |
| folder_path = f"Curriculum_Core/{clean_subject}/{semester}/{grade}/" | |
| all_files = _hf_api.list_repo_files(repo_id=VAULT_REPO_ID, repo_type="dataset") | |
| lesson_files = [f for f in all_files if f.startswith(folder_path) and f.lower().endswith(('.txt', '.md', '.json'))] | |
| choices = [] | |
| for f in sorted(lesson_files): | |
| display_name = re.sub(r'\.(txt|md|json)$', '', os.path.basename(f), flags=re.IGNORECASE) | |
| choices.append((display_name, f)) | |
| return choices | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดู ุณุฑุฏ ุงูุฏุฑูุณ: {e}") | |
| return [] | |
| def load_lesson_content(lesson_path): | |
| if not lesson_path: | |
| return None | |
| try: | |
| local_path = hf_hub_download(repo_id=VAULT_REPO_ID, filename=lesson_path, repo_type="dataset") | |
| with open(local_path, "r", encoding="utf-8") as f: | |
| content = f.read() | |
| return content[:MAX_LESSON_CHARS] if len(content) > MAX_LESSON_CHARS else content | |
| except Exception as e: | |
| print(f"โ ๏ธ ูุดู ุชุญู ูู ุงูุฏุฑุณ: {e}") | |
| return None | |
| def save_student_profile(grade, semester, subject, lesson_path, user_name, track=None): | |
| if not HF_TOKEN: | |
| return | |
| try: | |
| profile = { | |
| "user_name": user_name, "grade": grade, "semester": semester, | |
| "subject": subject, "lesson": lesson_path, "track": track, | |
| "last_updated": time.strftime("%Y-%m-%d %H:%M:%S") | |
| } | |
| _hf_api.upload_file( | |
| path_or_fileobj=json.dumps(profile, ensure_ascii=False, indent=2).encode("utf-8"), | |
| path_in_repo=f"Student_Hub/User_{user_name}/profile.json", | |
| repo_id=VAULT_REPO_ID, repo_type="dataset", token=HF_TOKEN | |
| ) | |
| except Exception as e: | |
| print(f"โ ๏ธ ุฎุทุฃ ูู ุงูุญูุธ: {e}") | |
| def upload_lesson_to_vault(file_obj, lesson_number, lesson_title, subject, semester, grade): | |
| if not HF_TOKEN: | |
| return "โ ๏ธ ูุง ููุฌุฏ HF_TOKEN ูู ุฃุณุฑุงุฑ ูุฐู ุงูู ุณุงุญุฉุ ูุง ูู ูู ุงูุฑูุน." | |
| if file_obj is None: | |
| return "โ ๏ธ ุงุฎุชุฑ ู ููุงู ุฃููุงู." | |
| match = re.search(r'\d+', str(lesson_number or "")) | |
| if not match: | |
| return "โ ๏ธ ุฃุฏุฎู ุฑูู ุงูุฏุฑุณ (ู ุซู: 1)." | |
| try: | |
| clean_subject = clean_subject_for_path(subject) | |
| num = int(match.group()) | |
| title = (lesson_title or "").strip() or "ุจุฏูู_ุนููุงู" | |
| ext = os.path.splitext(file_obj.name)[1] or ".txt" | |
| path_in_repo = f"Curriculum_Core/{clean_subject}/{semester}/{grade}/{num:02d}_{title}{ext}" | |
| _hf_api.upload_file( | |
| path_or_fileobj=file_obj.name, path_in_repo=path_in_repo, | |
| repo_id=VAULT_REPO_ID, repo_type="dataset", token=HF_TOKEN, | |
| ) | |
| return f"โ ุชู ุฑูุน ุงูุฏุฑุณ ุฅูู: {path_in_repo}" | |
| except Exception as e: | |
| return f"โ ๏ธ ูุดู ุงูุฑูุน: {e}" | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงูุตููู/ุงููุตูู/ุงูู ูุงุฏ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| GRADES = [ | |
| "ุงูุฑูุถุฉ", "ุงูุตู ุงูุฃูู", "ุงูุตู ุงูุซุงูู", "ุงูุตู ุงูุซุงูุซ", "ุงูุตู ุงูุฑุงุจุน", | |
| "ุงูุตู ุงูุฎุงู ุณ", "ุงูุตู ุงูุณุงุฏุณ", "ุงูุตู ุงูุณุงุจุน", "ุงูุตู ุงูุซุงู ู", "ุงูุตู ุงูุชุงุณุน", | |
| "ุงูุตู ุงูุนุงุดุฑ", "ุงูุตู ุงูุญุงุฏู ุนุดุฑ", "ุงูุตู ุงูุซุงูู ุนุดุฑ (ุงูุชูุฌููู)" | |
| ] | |
| SEMESTERS = ["ุงููุตู ุงูุฃูู", "ุงููุตู ุงูุซุงูู"] | |
| DEFAULT_SUBJECTS = ["๐งฎ ุงูุฑูุงุถูุงุช", "๐ฌ ุงูุนููู ", "๐ ุงููุบุฉ ุงูุนุฑุจูุฉ", "๐ ุงูุชุฑุจูุฉ ุงูุฅุณูุงู ูุฉ", "๐ค ุงููุบุฉ ุงูุฅูุฌููุฒูุฉ"] | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุฏุงูุฉ ุงูู ุญุงุฏุซุฉ ุงูุฑุฆูุณูุฉ ูุบุฑูุฉ ุงูุตู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def teacher_chat(audio_mic, direct_text, subject, grade, semester, lesson_path, track, student_name, | |
| chat_history_state, teacher_prefs, active_idx): | |
| t_start = time.time() | |
| if direct_text and direct_text.strip(): | |
| user_text = direct_text.strip() | |
| else: | |
| if audio_mic is None: | |
| return chat_history_state, None, "๐ค ุณุฌูู ุณุคุงูู ุฃู ุงุณุชุฎุฏู ุฃุญุฏ ุฃุฏูุงุชู" | |
| try: | |
| t0 = time.time() | |
| processed_audio = preprocess_student_audio(audio_mic) | |
| whisper = load_whisper() | |
| result = whisper(processed_audio, generate_kwargs={"language": "arabic"}) | |
| user_text = result["text"].strip() | |
| print(f"โฑ๏ธ ุงูู ุฑุญูุฉ ุงูุตูุชูุฉ ูุงู ูุฉ (ุชุทุจูุน+ููุงุชุฑ+Whisper): {time.time() - t0:.1f}s") | |
| except Exception as e: | |
| return chat_history_state, None, f"โ ๏ธ ุฎุทุฃ: {e}" | |
| if not user_text: | |
| return chat_history_state, None, "โ ูู ูุชู ุงูุชุนุฑู ุนูู ููุงู " | |
| print(f"๐ฃ๏ธ {student_name}: {user_text}") | |
| lesson_content = load_lesson_content(lesson_path) | |
| is_science = subject in ("๐งฎ ุงูุฑูุงุถูุงุช", "๐ฌ ุงูุนููู ") | |
| if is_science: | |
| t0 = time.time() | |
| phi_content = ask_science_teacher_via_controller(user_text, "teach", chat_history_state) | |
| print(f"โฑ๏ธ ุงุณุชุฏุนุงุก Controller/Phi: {time.time() - t0:.1f}s") | |
| t0 = time.time() | |
| if phi_content: | |
| cleaned_response, new_history = deliver_via_qwen(user_text, phi_content, chat_history_state) | |
| else: | |
| cleaned_response, new_history = ask_teacher(user_text, chat_history_state) | |
| print(f"โฑ๏ธ ุชูููุฏ ูููู (ู ุณุงุฑ ุนูู ู): {time.time() - t0:.1f}s") | |
| else: | |
| t0 = time.time() | |
| student_name_clean = (student_name or "ุงูุทุงูุจ").strip() | |
| context_str = f"ู ุงุฏุฉ {subject} - {semester}" + (f" - ุชุฎุตุต {track}" if track else "") | |
| system_prompt = ( | |
| f"ุฃูุช ุงูุฃุณุชุงุฐ ุนุจูุฏุ ู ุนูู ุฎุจูุฑ ูุฏูุฏ ุฌุฏุงู. ุชุชุญุฏุซ ุงูุขู ู ุน ุทุงูุจู {student_name_clean}. ุงูุณูุงู: {context_str}.\n" | |
| f"- ุฎุงุทุจ {student_name_clean} ุจุงุณู ู ู ู ููุช ูุขุฎุฑ ุจุดูู ุทุจูุนู.\n" | |
| "- ุฅุฐุง ูุงูุช ุชุญูุฉุ ุฑุฏ ุจุชุญูุฉ ูุฏูุฏุฉ ูุตูุฑุฉ ููุท.\n" | |
| "- ุฅุฐุง ูุงู ุณุคุงูุงู ุชุนููู ูุงู: ุฃุฌุจ ุจุฅูุฌุงุฒ (30 ููู ุฉ)ุ ุฎุทูุงุชุ ุณุคุงู ุฎุชุงู ู.\n" | |
| "- ุฅุฐุง ูุฑุฏุช ู ุนุงุฏูุฉ LaTeX ุจูู $$ุ ูุณูุฑูุง ุจูุถูุญ." | |
| ) | |
| if lesson_content: | |
| system_prompt += f"\n\n๐ ุงูุฏุฑุณ ุงูู ุฑุฌุนู (ู ุณุงุนุฏ ููุท):\n```\n{lesson_content}\n```" | |
| messages = [{"role": "system", "content": system_prompt}] + chat_history_state + [{"role": "user", "content": user_text}] | |
| tokenizer, model = load_qwen_lazy() | |
| 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.4, 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)] | |
| cleaned_response = tokenizer.batch_decode(out_ids, skip_special_tokens=True)[0].strip() | |
| gc.collect() # ุชุฎููู ุฐุฑูุฉ ุงูุฐุงูุฑุฉ ุจุนุฏ ูู ุชูููุฏ | |
| if not cleaned_response.endswith(('.', 'ุ', '!')): | |
| cleaned_response += '.' | |
| new_history = chat_history_state + [{"role": "user", "content": user_text}, {"role": "assistant", "content": cleaned_response}] | |
| print(f"โฑ๏ธ ุชูููุฏ ูููู (ู ุณุงุฑ ุฃุฏุจู/ูุบุงุช): {time.time() - t0:.1f}s") | |
| save_student_profile(grade, semester, subject, lesson_path, student_name, track) | |
| # ๐๏ธ ุตูุช "ุงูู ุญุงูุฑ" ููู ุญุงุฏุซุฉ ุงูุนุงุฏูุฉ ุญุงููุงู (ุฃุตูุงุช ุงูุดุฑุญ/ุงูุฃุณุฆูุฉ | |
| # ุณุชููุนููู ุชููุงุฆูุงู ุนูุฏ ุจูุงุก ู ูุทู ุงูุญุตุฉ ุงูู ูุณูู ุฉ ูุงุญูุงู) | |
| try: | |
| voice = teacher_prefs[active_idx]["ู ุญุงูุฑ"] | |
| except Exception: | |
| voice = "ar-JO-TaimNeural" | |
| t0 = time.time() | |
| audio_path = call_edge_tts_local(cleaned_response, voice) # ๐งช ู ุญูู ู ุจุงุดุฑ ุจุฏู ุงูุฑุงุฏุงุฑ (ููุชุฌุฑุจุฉ) | |
| print(f"โฑ๏ธ ุชูููุฏ ุงูุตูุช (Edge-TTS ู ุญูู): {time.time() - t0:.1f}s") | |
| print(f"โฑ๏ธ ุงูููุช ุงูุฅุฌู ุงูู ููุฏูุฑุฉ ูุงู ูุฉ: {time.time() - t_start:.1f}s") | |
| status = "โ ุชู ุงูุฑุฏ" if audio_path else "โ ๏ธ ูุดู ุงูุตูุช" | |
| return new_history, audio_path, status | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุฃุฏูุงุชู: ุขูุฉ ุญุงุณุจุฉ + ูุงู ูุฑุง OCR + ููุญุฉ ู ูุงุชูุญ ุจุฏููุฉ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| SYMPY_LOCALS = {"sin": sp.sin, "cos": sp.cos, "tan": sp.tan, "sqrt": sp.sqrt, "pi": sp.pi} | |
| CALC_BUTTONS = [ | |
| [("7", "7"), ("8", "8"), ("9", "9"), ("รท", "/"), ("โ", "sqrt(")], | |
| [("4", "4"), ("5", "5"), ("6", "6"), ("ร", "*"), ("^", "**")], | |
| [("1", "1"), ("2", "2"), ("3", "3"), ("-", "-"), ("ฯ", "pi")], | |
| [("0", "0"), (".", "."), ("(", "("), (")", ")"), ("+", "+")], | |
| [("sin", "sin("), ("cos", "cos("), ("tan", "tan("), ("C", "__CLEAR__"), ("โซ", "__BACK__")], | |
| ] | |
| def calc_append(current, token): | |
| return (current or "") + token | |
| def calc_clear(): | |
| return "" | |
| def calc_backspace(current): | |
| return (current or "")[:-1] | |
| def calc_evaluate(expr): | |
| if not expr or not expr.strip(): | |
| return expr, "โ ๏ธ ุฃุฏุฎู ุนุจุงุฑุฉ ุฃููุงู" | |
| try: | |
| return str(sp.sympify(expr, locals=SYMPY_LOCALS).evalf()), "โ ุชู ุงูุญุณุงุจ" | |
| except Exception as e: | |
| return expr, f"โ ๏ธ ุชุนุฐุฑ ุงูุญุณุงุจ: {e}" | |
| def calc_to_latex_question(expr): | |
| if not expr or not expr.strip(): | |
| return "" | |
| try: | |
| return f"ุงุดุฑุญ ูู ุฎุทูุงุช ุญู ูุฐู ุงูู ุณุฃูุฉ: $$ {sp.latex(sp.sympify(expr, locals=SYMPY_LOCALS))} $$" | |
| except Exception as e: | |
| return f"ุงุดุฑุญ ูู ุฎุทูุงุช ุญู ูุฐู ุงูู ุณุฃูุฉ: {expr}" | |
| _ocr_processor, _ocr_model = None, None | |
| def load_ocr(): | |
| global _ocr_processor, _ocr_model | |
| if _ocr_model is not None: | |
| return _ocr_processor, _ocr_model | |
| print("โณ ุชุญู ูู ูู ูุฐุฌ ูุฑุงุกุฉ ุงูุตูุฑ ุงูุนุฑุจู (TrOCR)...") | |
| _ocr_processor = TrOCRProcessor.from_pretrained(OCR_MODEL_ID) | |
| _ocr_model = VisionEncoderDecoderModel.from_pretrained(OCR_MODEL_ID) | |
| return _ocr_processor, _ocr_model | |
| def read_image_text(image_path): | |
| if not image_path: | |
| return "", "โ ๏ธ ูู ูุชู ุฑูุน ุตูุฑุฉ" | |
| try: | |
| processor, model = load_ocr() | |
| image = Image.open(image_path).convert("RGB") | |
| pixel_values = processor(images=image, return_tensors="pt").pixel_values | |
| generated_ids = model.generate(pixel_values, max_new_tokens=200) | |
| text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0] | |
| return text, "โ ุชู ุงุณุชุฎุฑุงุฌ ุงููุต" | |
| except Exception as e: | |
| return "", f"โ ๏ธ ุฎุทุฃ ูู ูุฑุงุกุฉ ุงูุตูุฑุฉ: {e}" | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงููุงุฌูุฉ | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with gr.Blocks(title="My Teacher Lesson", theme=gr.themes.Soft()) as demo: | |
| theme_html = gr.HTML(apply_theme("๐ ููุงุฑู")) | |
| gr.Markdown("# ๐ ุงูู ูุตุฉ ุงูุชุนููู ูุฉ ุงูุฐููุฉ ุงูู ูุญุฏุฉ") | |
| teacher_prefs_state = gr.State([dict(t) for t in DEFAULT_TEACHER_PREFS]) | |
| active_teacher_index_state = gr.State(0) | |
| chat_history_state = gr.State([]) | |
| subject_choices_state = gr.State(list(DEFAULT_SUBJECTS)) | |
| with gr.Tabs(): | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุชุจููุจ 1: ุบุฑูุฉ ุงูุตู (ู ุฏู ุฌุฉ ุจุงููุงู ู ู ู ู ูู Chat) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with gr.Tab("๐ซ ุบุฑูุฉ ุงูุตู"): | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| with gr.Row(): | |
| student_name_input = gr.Textbox(label="๐ค ุงุณู ุงูุทุงูุจ", value="Divid") | |
| grade_dropdown = gr.Dropdown(choices=GRADES, value="ุงูุตู ุงูุนุงุดุฑ", label="๐ ุงูุตู") | |
| with gr.Row(): | |
| semester_dropdown = gr.Dropdown(choices=SEMESTERS, value="ุงููุตู ุงูุฃูู", label="๐ ุงููุตู") | |
| track_dropdown = gr.Dropdown(choices=[], value=None, label="๐ฏ ุงูุชุฎุตุต (ุชูุฌููู)", visible=False, allow_custom_value=True) | |
| with gr.Row(visible=False) as track_row: | |
| new_track_input = gr.Textbox(label="โ ุชุฎุตุต ุฌุฏูุฏ") | |
| add_track_btn = gr.Button("๐พ ุญูุธ", size="sm") | |
| with gr.Row(): | |
| subject_dropdown = gr.Dropdown(choices=DEFAULT_SUBJECTS, value=DEFAULT_SUBJECTS[0], label="๐ ุงูู ุงุฏุฉ") | |
| add_subject_btn = gr.Button("โ ุฅุถุงูุฉ ู ูุงุฏ ุฃุฎุฑู", size="sm") | |
| with gr.Row(visible=False) as new_subject_row: | |
| new_subject_input = gr.Textbox(label="โ๏ธ ุงุณู ุงูู ุงุฏุฉ ุงูุฌุฏูุฏุฉ") | |
| confirm_subject_btn = gr.Button("๐พ ุญูุธ", size="sm") | |
| with gr.Row(): | |
| lesson_dropdown = gr.Dropdown(choices=[], label="๐ ุงูุฏุฑุณ (ู ู ุงูุฎุฒูุฉ)") | |
| refresh_lessons_btn = gr.Button("๐", size="sm") | |
| with gr.Accordion("๐ค ุฑูุน ุฏุฑุณ ุฌุฏูุฏ ุฅูู ุงูุฎุฒูุฉ", open=False): | |
| upload_lesson_number = gr.Textbox(label="ุฑูู ุงูุฏุฑุณ", placeholder="ู ุซุงู: 1") | |
| upload_lesson_title = gr.Textbox(label="ุนููุงู ุงูุฏุฑุณ", placeholder="ู ุซุงู: ุงูููู ุฉ ุงูู ูุฒููุฉ ููุฑูู ") | |
| upload_lesson_file = gr.File(label="ุงุฎุชุฑ ู ูู ุงูุฏุฑุณ ู ู ุฌูุงุฒู") | |
| upload_lesson_btn = gr.Button("๐พ ุญูุธ ูู ุงูุฎุฒูุฉ", variant="primary") | |
| upload_lesson_status = gr.Textbox(label="ุญุงูุฉ ุงูุฑูุน", interactive=False) | |
| chatbot_display = gr.Chatbot(label="ุงูุญูุงุฑ", height=400) | |
| mic_input = gr.Audio(label="๐ค ุณุคุงูู", type="filepath", sources=["microphone"]) | |
| send_btn = gr.Button("๐ ุฅุฑุณุงู", variant="primary") | |
| with gr.Accordion("๐ ๏ธ ุฃุฏูุงุชู", open=False): | |
| with gr.Tab("๐งฎ ุงูุขูุฉ ุงูุญุงุณุจุฉ ุงูุนูู ูุฉ"): | |
| calc_display = gr.Textbox(label="ุงูุนุจุงุฑุฉ", value="") | |
| for row in CALC_BUTTONS: | |
| with gr.Row(): | |
| for label, token in row: | |
| b = gr.Button(label, size="sm") | |
| if token == "__CLEAR__": | |
| b.click(fn=calc_clear, outputs=[calc_display], api_name=False) | |
| elif token == "__BACK__": | |
| b.click(fn=calc_backspace, inputs=[calc_display], outputs=[calc_display], api_name=False) | |
| else: | |
| b.click(fn=lambda cur, t=token: calc_append(cur, t), inputs=[calc_display], outputs=[calc_display], api_name=False) | |
| with gr.Row(): | |
| calc_eq_btn = gr.Button("๐ฐ ุญุณุงุจ", size="sm") | |
| calc_send_btn = gr.Button("๐ค ุญููู ูู LaTeX ูุฃุฑุณู", size="sm") | |
| calc_status = gr.Textbox(label="ุงูุญุงูุฉ", interactive=False) | |
| calc_pending_question = gr.Textbox(visible=False) | |
| calc_eq_btn.click(fn=calc_evaluate, inputs=[calc_display], outputs=[calc_display, calc_status], api_name=False) | |
| with gr.Tab("๐ท ุงููุงู ูุฑุง ููุฑุงุกุฉ ุงูุตูุฑ"): | |
| camera_input = gr.Image(label="ุตููุฑ/ุงุฑูุน ูุฑูุฉ ุงูุนู ู", type="filepath", sources=["upload", "webcam"]) | |
| ocr_btn = gr.Button("๐ ุงูุฑุฃ ุงููุต") | |
| ocr_text_out = gr.Textbox(label="ุงููุต ุงูู ุณุชุฎุฑุฌ", lines=3) | |
| ocr_status = gr.Textbox(label="ุงูุญุงูุฉ", interactive=False) | |
| ocr_send_btn = gr.Button("โก๏ธ ุฃุฑุณู ููุฃุณุชุงุฐ", variant="primary") | |
| ocr_btn.click(fn=read_image_text, inputs=[camera_input], outputs=[ocr_text_out, ocr_status], api_name=False) | |
| with gr.Tab("โจ๏ธ ููุญุฉ ุงูู ูุงุชูุญ ุงูุจุฏููุฉ"): | |
| emergency_text_in = gr.Textbox(label="ุงูุชุจ ุณุคุงูู ููุง", lines=3) | |
| emergency_send_btn = gr.Button("โก๏ธ ุฃุฑุณู ููุฃุณุชุงุฐ", variant="primary") | |
| with gr.Column(scale=1): | |
| audio_output = gr.Audio(label="๐ ุงูุฑุฏ", type="filepath", autoplay=True) | |
| status_output = gr.Textbox(label="ุงูุญุงูุฉ") | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุชุจููุจ 2: ุงูุฅุนุฏุงุฏุงุช ูุงูุชูุถููุงุช | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with gr.Tab("โ๏ธ ุงูุฅุนุฏุงุฏุงุช ูุงูุชูุถููุงุช"): | |
| gr.Markdown("### ๐จ ู ุธูุฑ ุงูู ูุตุฉ") | |
| theme_radio = gr.Radio(choices=list(THEME_CSS.keys()), value="๐ ููุงุฑู", label="ุงุฎุชุฑ ุงูู ุธูุฑ") | |
| gr.Markdown("---\n### ๐จโ๐ซ ุนุฏุฏ ุงูู ุนูู ูู ูุฃุตูุงุช ุงูุฃุฏูุงุฑ") | |
| teacher_count_radio = gr.Radio(choices=["2", "3"], value="2", label="ุนุฏุฏ ุงูู ุนูู ูู ุงูู ุชุงุญูู") | |
| active_teacher_radio = gr.Radio(choices=["ุงูู ุนูู ุงูุฃูู", "ุงูู ุนูู ุฉ ุงูุซุงููุฉ", "ุงูู ุนูู ุงูุซุงูุซ"], value="ุงูู ุนูู ุงูุฃูู", label="ุงูู ุนูู ุงููุดุท ุญุงููุงู ูู ุบุฑูุฉ ุงูุตู") | |
| teacher_setting_rows = [] | |
| for idx in range(3): | |
| with gr.Row(visible=(idx < 2)) as row: | |
| name_box = gr.Textbox(label=f"ุงุณู ุงูู ุนูู {idx + 1}", value=DEFAULT_TEACHER_PREFS[idx]["name"]) | |
| voice_dd_map = {} | |
| for role in VOICE_ROLES: | |
| voice_dd_map[role] = gr.Dropdown(choices=MICROSOFT_VOICES, value=DEFAULT_TEACHER_PREFS[idx][role], label=f"ุตูุช {role}") | |
| teacher_setting_rows.append((row, name_box, voice_dd_map)) | |
| save_settings_btn = gr.Button("๐พ ุญูุธ ุงูุชูุถููุงุช", variant="primary") | |
| settings_status = gr.Textbox(label="ุญุงูุฉ ุงูุญูุธ", interactive=False) | |
| def toggle_teacher_count(count): | |
| n = int(count) | |
| return [gr.update(visible=(i < n)) for i in range(3)] | |
| teacher_count_radio.change( | |
| fn=toggle_teacher_count, inputs=[teacher_count_radio], | |
| outputs=[r[0] for r in teacher_setting_rows], api_name=False | |
| ) | |
| def save_all_settings(*args): | |
| # args = name1, v1_ู ุญุงูุฑ, v1_ุดุฑุญ, v1_ุฃุณุฆูุฉ, name2, ..., name3, ... | |
| prefs = [] | |
| i = 0 | |
| names_voices = list(args) | |
| for t_idx in range(3): | |
| name = names_voices[i]; i += 1 | |
| roles = {} | |
| for role in VOICE_ROLES: | |
| roles[role] = names_voices[i]; i += 1 | |
| prefs.append({"name": name, **roles}) | |
| return prefs, "โ ุชู ุญูุธ ุงูุชูุถููุงุช" | |
| all_setting_inputs = [] | |
| for row, name_box, voice_dd_map in teacher_setting_rows: | |
| all_setting_inputs.append(name_box) | |
| for role in VOICE_ROLES: | |
| all_setting_inputs.append(voice_dd_map[role]) | |
| save_settings_btn.click( | |
| fn=save_all_settings, inputs=all_setting_inputs, | |
| outputs=[teacher_prefs_state, settings_status], api_name=False | |
| ) | |
| def set_active_teacher(choice): | |
| mapping = {"ุงูู ุนูู ุงูุฃูู": 0, "ุงูู ุนูู ุฉ ุงูุซุงููุฉ": 1, "ุงูู ุนูู ุงูุซุงูุซ": 2} | |
| return mapping.get(choice, 0) | |
| active_teacher_radio.change(fn=set_active_teacher, inputs=[active_teacher_radio], outputs=[active_teacher_index_state], api_name=False) | |
| theme_radio.change(fn=apply_theme, inputs=[theme_radio], outputs=[theme_html], api_name=False) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # ุงูุชุจููุจุงุช 3-6: ุญุฌุฒ ู ูุงู โ ุณูุจูููุง ูุงุญุฏุฉ ุชูู ุงูุฃุฎุฑู | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with gr.Tab("๐๏ธ ุฎุทุท ุงูุฏุฑูุณ ูุงูุชูุณูู "): | |
| gr.Markdown("๐ง **ููุฏ ุงูุชุทููุฑ.** ุณููุจูู ูุงุญูุงู ุจุงูุชุนุงูู ู ุน ูููู ูPhi.") | |
| with gr.Tab("๐ ุงูู ุณุชููุงุช ูุชุญููู ุงููุฌูุฉ ุงูุชุนููู ูุฉ"): | |
| gr.Markdown("๐ง **ููุฏ ุงูุชุทููุฑ.**") | |
| with gr.Tab("๐ฌ ุนูุงูุฉ ุงูุทุงูุจ ูุงูุฃุณุชุงุฐ"): | |
| gr.Markdown("๐ง **ููุฏ ุงูุชุทููุฑ.**") | |
| with gr.Tab("๐ผ๏ธ ุฃุฑุดูู ูุฅุฏุงุฑุฉ ุงููุณุงุฆุท"): | |
| gr.Markdown("๐ง **ููุฏ ุงูุชุทููุฑ.**") | |
| # โโโโ ุฑุจุท ุงูุฃุญุฏุงุซ: ุบุฑูุฉ ุงูุตู โโโโ | |
| def toggle_track(grade): | |
| if "ุงูุชูุฌููู" in grade: | |
| return gr.update(visible=True), gr.update(visible=True), gr.update(visible=True) | |
| return gr.update(visible=False, value=None), gr.update(visible=False), gr.update(visible=False) | |
| grade_dropdown.change(fn=toggle_track, inputs=[grade_dropdown], outputs=[track_dropdown, track_row, add_track_btn], api_name=False) | |
| add_subject_btn.click(fn=lambda: gr.update(visible=True), outputs=[new_subject_row], api_name=False) | |
| def confirm_new_subject(new_subject, choices): | |
| new_subject = (new_subject or "").strip() | |
| if not new_subject: | |
| return gr.update(), choices, gr.update(visible=False), "" | |
| if new_subject not in choices: | |
| choices = choices + [new_subject] | |
| return gr.update(choices=choices, value=new_subject), choices, gr.update(visible=False), "" | |
| confirm_subject_btn.click(fn=confirm_new_subject, inputs=[new_subject_input, subject_choices_state], | |
| outputs=[subject_dropdown, subject_choices_state, new_subject_row, new_subject_input], api_name=False) | |
| def refresh_lessons(grade, semester, subject): | |
| lessons = list_lessons_in_vault(grade, semester, subject) | |
| return gr.update(choices=lessons, value=(lessons[0][1] if lessons else None)) | |
| grade_dropdown.change(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False) | |
| semester_dropdown.change(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False) | |
| subject_dropdown.change(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False) | |
| refresh_lessons_btn.click(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False) | |
| demo.load(fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown]) | |
| upload_lesson_btn.click( | |
| fn=upload_lesson_to_vault, | |
| inputs=[upload_lesson_file, upload_lesson_number, upload_lesson_title, subject_dropdown, semester_dropdown, grade_dropdown], | |
| outputs=[upload_lesson_status], | |
| api_name=False, | |
| ).then( | |
| fn=refresh_lessons, inputs=[grade_dropdown, semester_dropdown, subject_dropdown], outputs=[lesson_dropdown], api_name=False | |
| ) | |
| def sanitize_history_for_chatbot(history): | |
| """๐ก๏ธ ุชูุธูู ุฏูุงุนู ููุงุฆู ูุตุงุฑู : ุฃูุงู ูุงู ู ุตุฏุฑ ุงูุนุทู (Phiุ ููููุ | |
| ุชุณูุณู ุบูุฑ ู ุชููุน)ุ ูุฐุง ูุถู ู ุฃู ูู ุนูุตุฑ ูุตู ูู Chatbot ูู dict | |
| ูุธูู 100% ุจู ูุชุงุญู role/content ููุท ูููู ูุตูุฉ ุตุงููุฉ โ ูู ูุน ุชูุฑุงุฑ | |
| ุฎุทุฃ 'Data incompatible with messages format' ููุงุฆูุงู.""" | |
| clean = [] | |
| for msg in (history or []): | |
| if isinstance(msg, dict) and "role" in msg and "content" in msg: | |
| role = str(msg["role"]).strip() | |
| content = str(msg["content"]).strip() | |
| if role and content: | |
| clean.append({"role": role, "content": content}) | |
| return clean | |
| def process_and_display(audio_mic, direct_text, subject, grade, semester, lesson_path, track, student_name, | |
| history_state, teacher_prefs, active_idx): | |
| new_history, audio, status = teacher_chat(audio_mic, direct_text, subject, grade, semester, lesson_path, | |
| track, student_name, history_state, teacher_prefs, active_idx) | |
| clean_history = sanitize_history_for_chatbot(new_history) | |
| return clean_history, clean_history, audio, status, None, "" | |
| common_inputs_tail = [subject_dropdown, grade_dropdown, semester_dropdown, lesson_dropdown, track_dropdown, | |
| student_name_input, chat_history_state, teacher_prefs_state, active_teacher_index_state] | |
| send_btn.click( | |
| fn=process_and_display, | |
| inputs=[mic_input, calc_pending_question] + common_inputs_tail, | |
| outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, calc_pending_question], | |
| api_name=False, | |
| ) | |
| calc_send_btn.click(fn=calc_to_latex_question, inputs=[calc_display], outputs=[calc_pending_question], api_name=False).then( | |
| fn=process_and_display, | |
| inputs=[mic_input, calc_pending_question] + common_inputs_tail, | |
| outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, calc_pending_question], | |
| api_name=False, | |
| ) | |
| ocr_send_btn.click( | |
| fn=process_and_display, | |
| inputs=[mic_input, ocr_text_out] + common_inputs_tail, | |
| outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, ocr_text_out], | |
| api_name=False, | |
| ) | |
| emergency_send_btn.click( | |
| fn=process_and_display, | |
| inputs=[mic_input, emergency_text_in] + common_inputs_tail, | |
| outputs=[chat_history_state, chatbot_display, audio_output, status_output, mic_input, emergency_text_in], | |
| api_name=False, | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860) | |