# ═══════════════════════════════════════════════════════════ # app/app.py — Marathi Mitra # Kid-friendly Marathi vocabulary learning app # # Run locally: python app/app.py # Deploy: Hugging Face Spaces (Gradio SDK) # # Local note: Runs on CPU — slow (~2-3 mins per word) # Use HF Spaces for fast GPU inference # ═══════════════════════════════════════════════════════════ import os import re import torch import tempfile import gradio as gr from dotenv import load_dotenv from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig from gtts import gTTS from peft import PeftModel # ── Load credentials ────────────────────────────────────────── # Works in all environments: # Colab: uses userdata, HF Spaces: uses os.getenv, Local: uses .env try: from google.colab import userdata HF_TOKEN = userdata.get("HF_TOKEN") HF_USERNAME = userdata.get("HF_USERNAME") except ImportError: load_dotenv("../.env") HF_TOKEN = os.getenv("HF_TOKEN") HF_USERNAME = os.getenv("HF_USERNAME", "ninadp") # ── Fix 1: Use v2 model ─────────────────────────────────────── MODEL_REPO = f"{HF_USERNAME}/marathi-mitra-phi3-v2" # ── Quick word categories ───────────────────────────────────── CATEGORIES = { "🌿 Nature": ["sun", "moon", "rain", "flower", "tree", "river", "sky", "water", "mountain"], "🐾 Animals": ["cat", "dog", "bird", "fish", "elephant", "cow", "monkey", "parrot", "butterfly"], "👨👩👧 Family": ["mother", "father", "sister", "brother", "grandmother", "grandfather"], "🏫 Daily": ["school", "book", "pencil", "food", "house", "friend"], } # ── Load model ──────────────────────────────────────────────── def load_model(): BASE_MODEL = "microsoft/Phi-3-mini-4k-instruct" ADAPTER = MODEL_REPO use_gpu = torch.cuda.is_available() print(f"Step 1: Loading base model...") print(f" {BASE_MODEL}") if use_gpu: bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True, ) base_model = AutoModelForCausalLM.from_pretrained( BASE_MODEL, quantization_config=bnb_config, device_map="auto", trust_remote_code=True, attn_implementation="eager", ) else: base_model = AutoModelForCausalLM.from_pretrained( BASE_MODEL, torch_dtype=torch.float16, trust_remote_code=True, low_cpu_mem_usage=True, attn_implementation="eager", ) print(f"Step 2: Loading adapter...") print(f" {ADAPTER}") model = PeftModel.from_pretrained( base_model, ADAPTER, token=HF_TOKEN, ) model.eval() print(f"Step 3: Loading tokenizer...") tokenizer = AutoTokenizer.from_pretrained( BASE_MODEL, trust_remote_code=True, ) tokenizer.pad_token = tokenizer.eos_token tokenizer.padding_side = "right" device = "GPU ✅" if use_gpu else "CPU ⚠️" print(f"✅ Model ready on {device}") return model, tokenizer # Load once at startup model, tokenizer = load_model() # ── Generate Marathi lesson ─────────────────────────────────── def generate_lesson(word: str) -> str: word = word.strip().lower() if not word: return "" prompt = f"""### Instruction: You are Marathi Mitra, a friendly Marathi teacher for kids. When given an English word, teach it in Marathi with the word in Devanagari script, pronunciation, a simple story sentence, and a fun fact. Always be encouraging and kid-friendly. ### Input: Teach me the Marathi word for: {word} ### Response: """ device = next(model.parameters()).device inputs = tokenizer( prompt, return_tensors="pt", truncation=True, max_length=512, ).to(device) with torch.no_grad(): output_ids = model.generate( **inputs, max_new_tokens=150, do_sample=False, pad_token_id=tokenizer.eos_token_id, eos_token_id=tokenizer.eos_token_id, repetition_penalty=1.3, ) full_text = tokenizer.decode(output_ids[0], skip_special_tokens=True) return full_text.split("### Response:")[-1].strip() # ── Text to Speech ──────────────────────────────────────────── def text_to_speech(lesson: str): if not lesson: return None try: devanagari = re.findall(r"[\u0900-\u097F]+", lesson) if not devanagari: return None marathi_word = " ".join(devanagari[:3]) tts = gTTS(text=marathi_word, lang="mr", slow=True) tmp = tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) tts.save(tmp.name) return tmp.name except Exception as e: print(f"TTS error: {e}") return None # ── Score and streak ────────────────────────────────────────── def format_status(score, streak): fire = "🔥" * min(streak, 5) stars = "⭐" * min(score, 10) return ( f'
माझा मराठी मित्र — Your Marathi Learning Friend!