"""Flask entry point for Language Tutor HF Space.""" import os import base64 import logging from flask import Flask, request, jsonify, render_template from backend.language_config import get_curriculum_data, LANGUAGES from backend.teacher_profiles import get_all_teachers from backend.session_manager import sessions from backend.tutor_engine import build_system_prompt, build_greeting_prompt, evaluate_pronunciation from backend.hf_client import chat_completion, speech_to_text, get_model_info logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = Flask(__name__, static_folder="static", template_folder="templates") @app.route("/") def index(): return render_template("index.html") @app.route("/api/health", methods=["GET"]) def health(): return jsonify({"status": "ok", "models": get_model_info()}) @app.route("/api/curriculum", methods=["GET"]) def curriculum(): return jsonify(get_curriculum_data()) @app.route("/api/teachers", methods=["GET"]) def teachers(): return jsonify({"teachers": get_all_teachers()}) @app.route("/api/teacher/voice-sample", methods=["POST"]) def teacher_voice_sample(): """Return sample text for client-side TTS preview.""" data = request.json or {} target_lang = data.get("target_lang", "hindi") lang = LANGUAGES.get(target_lang) if not lang: return jsonify({"error": "Unknown language"}), 400 return jsonify({"text": lang.get("sample_text", "Hello!")}) @app.route("/api/session/start", methods=["POST"]) def session_start(): data = request.json or {} target_lang = data.get("target_lang") instruction_lang = data.get("instruction_lang", "english") level = data.get("level", "A1") topic = data.get("topic", "greetings") teacher_id = data.get("teacher_id", "anaya") if not target_lang or target_lang not in LANGUAGES: return jsonify({"error": "Invalid target language"}), 400 if instruction_lang not in LANGUAGES: return jsonify({"error": "Invalid instruction language"}), 400 session = sessions.create_session(target_lang, instruction_lang, level, topic, teacher_id) system_prompt = build_system_prompt(target_lang, instruction_lang, level, topic, teacher_id) session.add_message("system", system_prompt) greeting_prompt = build_greeting_prompt(target_lang, instruction_lang, level, topic, teacher_id) session.add_message("user", greeting_prompt) result = chat_completion(session.get_messages()) greeting = result.get("content", "Hello! Let's begin our lesson.") session.add_message("assistant", greeting) return jsonify({ "session_id": session.session_id, "greeting": greeting, "teacher_id": teacher_id }) @app.route("/api/chat", methods=["POST"]) def chat(): data = request.json or {} session_id = data.get("session_id") user_message = data.get("message", "").strip() if not session_id or not user_message: return jsonify({"error": "session_id and message required"}), 400 session = sessions.get_session(session_id) if not session: return jsonify({"error": "Session expired or not found"}), 404 session.add_message("user", user_message) result = chat_completion(session.get_messages()) reply = result.get("content", "") session.add_message("assistant", reply) session.add_xp(5) return jsonify({ "reply": reply, "xp": session.xp, "model": result.get("model", "") }) @app.route("/api/voice", methods=["POST"]) def voice(): session_id = request.form.get("session_id") if not session_id: return jsonify({"error": "session_id required"}), 400 session = sessions.get_session(session_id) if not session: return jsonify({"error": "Session expired or not found"}), 404 audio_file = request.files.get("audio") if not audio_file: return jsonify({"error": "No audio file provided"}), 400 audio_bytes = audio_file.read() transcribed = speech_to_text(audio_bytes) if not transcribed: return jsonify({"error": "Could not transcribe audio", "transcribed": ""}), 200 session.add_message("user", transcribed) result = chat_completion(session.get_messages()) reply = result.get("content", "") session.add_message("assistant", reply) session.add_xp(10) return jsonify({ "transcribed": transcribed, "reply": reply, "xp": session.xp }) @app.route("/api/pronunciation/score", methods=["POST"]) def pronunciation_score(): data = request.json or {} session_id = data.get("session_id") expected = data.get("expected_text", "") audio_b64 = data.get("audio") if not expected: return jsonify({"error": "expected_text required"}), 400 if audio_b64: audio_bytes = base64.b64decode(audio_b64) transcribed = speech_to_text(audio_bytes) else: transcribed = data.get("user_text", "") result = evaluate_pronunciation(transcribed, expected) if session_id: session = sessions.get_session(session_id) if session and result["score"] >= 70: session.add_xp(15) result["xp_earned"] = 15 return jsonify(result) @app.route("/api/progress", methods=["GET"]) def get_progress(): session_id = request.args.get("session_id") if not session_id: return jsonify({"error": "session_id required"}), 400 session = sessions.get_session(session_id) if not session: return jsonify({"error": "Session not found"}), 404 return jsonify(session.to_dict()) if __name__ == "__main__": port = int(os.environ.get("PORT", 7860)) app.run(host="0.0.0.0", port=port, debug=False)