|
|
| from flask import Flask, render_template, request, jsonify, send_file |
| from flask_cors import CORS |
| import os |
| import uuid |
| import soundfile as sf |
| import logging |
| import json |
| from datetime import datetime |
|
|
| |
| os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" |
|
|
| |
| |
| |
| |
| |
| from src.brain import get_ai_response, FAIL_SAFE_SANTALI |
|
|
| try: |
| from src.utils import setup_logger |
| except ImportError: |
| |
| import logging |
| def setup_logger(name): |
| l = logging.getLogger(name) |
| l.addHandler(logging.StreamHandler()) |
| l.setLevel(logging.INFO) |
| return l |
|
|
|
|
| |
| app = Flask(__name__, static_folder="web/static", template_folder="web/templates") |
| CORS(app) |
|
|
| logger = setup_logger("WebServer") |
|
|
| |
| asr_model = None |
| mt_model = None |
| tts_model = None |
|
|
| |
| init_errors = { |
| "asr": None, |
| "mt": None, |
| "tts": None |
| } |
|
|
| def init_models(): |
| global asr_model, mt_model, tts_model, init_errors |
| logger.info("Initializing Models...") |
| |
| try: |
| from src.asr_provider import SantaliASR |
| from src.mt_provider import SantaliTranslator |
| from src.tts_provider import SantaliTTS |
| |
| logger.info("Imports successful, instantiating...") |
| |
| if not asr_model: |
| try: |
| asr = SantaliASR() |
| if asr.load_model(): |
| asr_model = asr |
| else: |
| init_errors["asr"] = "Load failed (check logs)" |
| logger.error("ASR Model failed to load.") |
| except Exception as e: |
| init_errors["asr"] = str(e) |
| logger.error(f"ASR Init Error: {e}") |
| |
| if not mt_model: |
| try: |
| mt = SantaliTranslator() |
| if mt.load_model(): |
| mt_model = mt |
| else: |
| init_errors["mt"] = "Load failed" |
| except Exception as e: |
| init_errors["mt"] = str(e) |
| logger.error(f"MT Init Error: {e}") |
|
|
| if not tts_model: |
| try: |
| tts = SantaliTTS() |
| if tts.load_model(): |
| tts_model = tts |
| else: |
| init_errors["tts"] = "Load failed" |
| except Exception as e: |
| init_errors["tts"] = str(e) |
| logger.error(f"TTS Init Error: {e}") |
| |
| except Exception as e: |
| logger.error(f"CRITICAL: Failed to initialize AI models due to dependency error: {e}") |
| init_errors["asr"] = f"Dependency Error: {e}" |
| logger.warning("Server will run in text-only/simulation mode if possible.") |
|
|
|
|
| |
| HISTORY_FILE = "chat_history.json" |
|
|
| def load_history(): |
| if not os.path.exists(HISTORY_FILE): |
| return [] |
| try: |
| with open(HISTORY_FILE, "r", encoding="utf-8") as f: |
| return json.load(f) |
| except Exception as e: |
| logger.error(f"Failed to load history: {e}") |
| return [] |
|
|
| def save_interaction(query_santali, query_english, response_english, response_santali, audio_filename=None): |
| history = load_history() |
| |
| interaction = { |
| "id": str(uuid.uuid4()), |
| "timestamp": datetime.now().isoformat(), |
| "query_santali": query_santali, |
| "query_english": query_english, |
| "response_english": response_english, |
| "response_santali": response_santali, |
| "audio_url": f"/api/audio/{audio_filename}" if audio_filename else None |
| } |
| |
| history.append(interaction) |
| |
| try: |
| with open(HISTORY_FILE, "w", encoding="utf-8") as f: |
| json.dump(history, f, indent=2, ensure_ascii=False) |
| except Exception as e: |
| logger.error(f"Failed to save history: {e}") |
|
|
| @app.route('/api/history', methods=['GET']) |
| def get_history_route(): |
| return jsonify(load_history()) |
|
|
| @app.route('/api/history', methods=['DELETE']) |
| def clear_history_route(): |
| try: |
| if os.path.exists(HISTORY_FILE): |
| os.remove(HISTORY_FILE) |
| logger.info("Chat history cleared.") |
| return jsonify({"status": "success"}) |
| except Exception as e: |
| logger.error(f"Failed to clear history: {e}") |
| return jsonify({"error": str(e)}), 500 |
|
|
| @app.route('/') |
| def index(): |
| return render_template('index.html') |
|
|
| @app.route('/api/status', methods=['GET']) |
| def status(): |
| return jsonify({ |
| "asr": asr_model is not None and asr_model.model is not None, |
| "mt": mt_model is not None and mt_model.model_indic_en is not None and mt_model.model_en_indic is not None, |
| "tts": tts_model is not None and tts_model.model is not None, |
| "errors": init_errors |
| }) |
|
|
| @app.route('/api/process', methods=['POST']) |
| def process_audio(): |
| if 'audio' not in request.files: |
| return jsonify({"error": "No audio file provided"}), 400 |
| |
| audio_file = request.files['audio'] |
| mode = request.form.get('mode', 'auto') |
| mt_mode = request.form.get('mt_mode', 'indictrans2') |
| |
| session_id = str(uuid.uuid4()) |
| input_filename = f"temp_input_{session_id}.wav" |
| output_filename = f"temp_output_{session_id}.wav" |
| |
| try: |
| |
| audio_file.save(input_filename) |
| |
| |
| ffmpeg_exe = "ffmpeg.exe" if os.path.exists("ffmpeg.exe") else "ffmpeg" |
| clean_input_filename = f"clean_input_{session_id}.wav" |
| |
| |
| |
| |
| |
| |
| |
| convert_cmd = [ |
| ffmpeg_exe, "-y", |
| "-i", input_filename, |
| "-af", "silenceremove=start_periods=1:start_threshold=-60dB:start_duration=0.1s,highpass=f=200,lowpass=f=3000,afftdn=nf=-25,dynaudnorm=f=150:g=15", |
| "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", |
| clean_input_filename |
| ] |
| |
| try: |
| import subprocess |
| subprocess.check_output(convert_cmd, stderr=subprocess.STDOUT) |
| processing_file = clean_input_filename |
| except Exception as e: |
| logger.error(f"FFmpeg conversion failed: {e}") |
| processing_file = input_filename |
|
|
| |
| logger.info(f"Processing audio: {processing_file} [Mode: {mode}]") |
| |
| santali_query = asr_model.transcribe(processing_file, language='sat') |
| logger.info(f"ASR Identified: {santali_query}") |
| |
| if not santali_query: |
| return jsonify({"error": "Could not understand audio"}), 200 |
| |
| |
| |
| |
| english_query = mt_model.translate(santali_query, src_lang='sat', tgt_lang='eng', model_type=mt_mode) |
| logger.info(f"MT (Sat->Eng): {english_query}") |
| |
| |
| from src.cache import check_cache, save_to_cache |
| from datetime import datetime |
| |
| |
| cached_santali, similarity, matched_q, original_backend = check_cache(english_query) |
| |
| |
| logger.info(f"Checking cache for English question: '{english_query}' at {datetime.now().isoformat()}") |
| |
| if cached_santali: |
| |
| logger.info("CACHE_HIT") |
| logger.info(f"Similarity Score: {similarity:.2f}") |
| logger.info(f"Matched Question: {matched_q}") |
| logger.info(f"Backend Originally Used: {original_backend}") |
| logger.info("ASR confidence: N/A") |
| |
| |
| santali_response = cached_santali |
| |
| |
| raw_english_response = f"Cached Answer (Original question matched: {matched_q})" |
| english_response_tagged = f"{raw_english_response} [CACHE HIT: {original_backend} | Sim: {similarity:.2f}]" |
| source = f"CACHE ({original_backend})" |
| else: |
| |
| logger.info("CACHE_MISS") |
| logger.info(f"Similarity Score: {similarity:.2f}") |
| logger.info("Backend Originally Used: N/A") |
| logger.info("ASR confidence: N/A") |
| |
| |
| history_context = [] |
| try: |
| full_history = load_history() |
| recent_history = full_history[-5:] if len(full_history) > 5 else full_history |
| for item in recent_history: |
| history_context.append({"role": "user", "content": item.get("query_english", "")}) |
| prev_resp = item.get("response_english", "") |
| if "[LLM USED:" in prev_resp: |
| prev_resp = prev_resp.split("[LLM USED:")[0].strip() |
| history_context.append({"role": "model", "content": prev_resp}) |
| except Exception as e: |
| logger.warning(f"History error: {e}") |
|
|
| |
| brain_out = get_ai_response( |
| text=english_query, |
| santali_text=santali_query, |
| conversation_history=history_context, |
| mode=mode |
| ) |
| |
| if isinstance(brain_out, dict): |
| raw_english_response = brain_out.get("text", "") |
| source = brain_out.get("source", "UNKNOWN") |
| is_fallback = brain_out.get("santali_fallback", False) |
| else: |
| raw_english_response = str(brain_out) |
| source = "UNKNOWN" |
| is_fallback = False |
|
|
| if is_fallback and source in ["FAIL", "EMPTY_INPUT"]: |
| santali_response = FAIL_SAFE_SANTALI |
| english_response_tagged = f"System Failure. [ANSWER SOURCE: {source}]" |
| elif source == "SAFETY_GUARD": |
| santali_response = FAIL_SAFE_SANTALI |
| english_response_tagged = f"{raw_english_response} [SAFETY_GUARD]" |
| else: |
| |
| santali_response = mt_model.translate(raw_english_response, src_lang='eng', tgt_lang='sat', model_type=mt_mode) |
| english_response_tagged = f"{raw_english_response} [ANSWER SOURCE: {source}]" |
| |
| |
| |
| save_to_cache(english_query, santali_response, source, 0.0) |
| |
| logger.info(f"AI Response ({source}): {raw_english_response}") |
| |
| logger.info(f"Response (Sat): {santali_response}") |
| |
| |
| audio_path = tts_model.speak_to_file(santali_response, output_filename) |
| |
| |
| response_data = { |
| "query_santali": santali_query, |
| "query_english": english_query, |
| "response_english": english_response_tagged, |
| "response_santali": santali_response, |
| "llm_source": source |
| } |
| |
| if audio_path and os.path.exists(audio_path): |
| response_data["audio_url"] = f"/api/audio/{output_filename}" |
| |
| |
| save_interaction( |
| santali_query, |
| english_query, |
| english_response_tagged, |
| santali_response, |
| output_filename if (audio_path and os.path.exists(audio_path)) else None |
| ) |
|
|
| return jsonify(response_data) |
|
|
| except Exception as e: |
| logger.error(f"Processing error: {e}") |
| import traceback |
| traceback.print_exc() |
| return jsonify({"error": str(e)}), 500 |
| finally: |
| if os.path.exists(input_filename): os.remove(input_filename) |
| if 'clean_input_filename' in locals() and os.path.exists(clean_input_filename): os.remove(clean_input_filename) |
|
|
| @app.route('/api/audio/<filename>') |
| def get_audio(filename): |
| return send_file(filename, mimetype="audio/wav") |
|
|
| if __name__ == "__main__": |
| logger.info("--- Server Starting ---") |
| try: |
| import torch |
| import torchaudio |
| logger.info(f"Torch: {torch.__version__}") |
| logger.info(f"TorchAudio: {torchaudio.__version__}") |
| logger.info(f"CUDA Available: {torch.cuda.is_available()}") |
| if torch.cuda.is_available(): |
| logger.info(f"CUDA Device: {torch.cuda.get_device_name(0)}") |
| except ImportError as e: |
| logger.error(f"Environment Error: {e}") |
| |
| init_models() |
| |
| port = int(os.environ.get('PORT', 5000)) |
| |
| app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False) |
|
|