# STARTUP: # 1. pip install -r requirements.txt # 2. python app.py ← start the app (auto-builds index) import os import io import numpy as np import soundfile as sf import gradio as gr import stt import llm import tts import rag_pipeline # ───────────────────────────────────────────── # AUTO INGEST — builds ChromaDB on first startup # Runs automatically if chroma_db folder not found # ───────────────────────────────────────────── import os if not os.path.exists("./chroma_db"): print("ChromaDB not found — building knowledge base index...") try: from knowledge_base import KNOWLEDGE_BASE from sentence_transformers import SentenceTransformer import chromadb print("Loading embedding model for ingest...") _embedder = SentenceTransformer( 'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2' ) _chroma = chromadb.PersistentClient(path="./chroma_db") _collection = _chroma.get_or_create_collection("banking_hindi") _documents = [] _metadatas = [] _ids = [] for doc in KNOWLEDGE_BASE: full_text = f"{doc['title']}\n{doc['content']}" _documents.append(full_text) _metadatas.append({ "id": doc["id"], "title": doc["title"], "category": doc["category"] }) _ids.append(doc["id"]) print(f"Embedding {len(_documents)} documents... (takes 2-3 min on first run)") _embeddings = _embedder.encode( _documents, show_progress_bar=True, batch_size=8 ).tolist() _collection.add( documents=_documents, embeddings=_embeddings, metadatas=_metadatas, ids=_ids ) print(f"✅ ChromaDB ready — {len(_documents)} documents indexed") # Cleanup temp variables del _embedder, _chroma, _collection del _documents, _metadatas, _ids, _embeddings except Exception as e: print(f"⚠️ Auto-ingest failed: {e}. Will use keyword retrieval fallback.") else: print("✅ ChromaDB found — skipping ingest") # ───────────────────────────────────────────── # CONFIG # ───────────────────────────────────────────── HF_TOKEN = ( os.environ.get("HF_TOKEN") or os.environ.get("HF_API_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN") or "" ) print(f"DEBUG app.py: HF_TOKEN loaded = {bool(HF_TOKEN)}, length = {len(HF_TOKEN)}") # (STT, LLM, and TTS functions moved to separate modules) # ───────────────────────────────────────────── # PIPELINE GENERATORS # ───────────────────────────────────────────── def run_voice_pipeline(audio_input): """Voice mode: mic audio → transcript, answer, citations, audio, status.""" if audio_input is None: yield "", "कृपया माइक्रोफोन बटन दबाकर अपना प्रश्न पूछें।", "", None, "🔴 *तैयार*" return if not HF_TOKEN: yield "", "⚠️ HF_TOKEN Secret नहीं मिला। Space Settings → Secrets में जोड़ें।", "", None, "❌ *त्रुटि*" return try: yield "", "", "", None, "🎙️ **सुन रहा हूँ...** (STT)" sample_rate, audio_array = audio_input # Ensure STT gets proper data transcript = stt.stt_whisper(audio_array, sample_rate) if not transcript.strip(): yield "", "आवाज़ समझ नहीं आई। कृपया फिर से बोलें।", "", None, "⚠️ *फिर से प्रयास करें*" return yield transcript, "", "", None, "🔎 **जानकारी खोज रहा हूँ...** (RAG)" normalized = rag_pipeline.normalize_jargon(transcript) eng_query = rag_pipeline.translate_to_retrieval_query(normalized) context_docs = rag_pipeline.retrieve_with_embeddings(eng_query, top_k=3) citations = "\n".join([f"• {doc['title']}" for doc in context_docs]) if context_docs else "" yield transcript, "", "", None, "🤖 **उत्तर तैयार कर रहा हूँ...** (LLM)" context = "\n\n".join( f"[{i+1}] {d['title']}\n{d['content'][:500]}" for i, d in enumerate(context_docs) ) if context_docs else "कोई प्रासंगिक जानकारी नहीं मिली।" full_prompt = ( f"नीचे दी गई जानकारी के आधार पर प्रश्न का उत्तर दें:\n\n" f"{context}\n\n" f"प्रश्न: {transcript}" ) answer = llm.llm_generate(full_prompt) yield transcript, answer, citations, None, "🔊 **आवाज़ बना रहा हूँ...** (TTS)" clean_text = rag_pipeline.format_response_for_tts(answer) audio_out = tts.tts_hindi(clean_text) yield transcript, answer, citations, audio_out, "✅ **पूरा हुआ**" except Exception as e: import traceback; traceback.print_exc() print(f"Pipeline error: {e}") yield "", f"⚠️ त्रुटि: {e}", "", None, "❌ *विफल*" def run_text_pipeline(text_input): """Text mode: typed question → answer, citations, audio, status.""" if not text_input or not text_input.strip(): yield text_input, "कृपया एक प्रश्न लिखें।", "", None, "🔴 *तैयार*" return if not HF_TOKEN: yield text_input, "⚠️ HF_TOKEN Secret नहीं मिला।", "", None, "❌ *त्रुटि*" return try: yield text_input, "", "", None, "🔎 **जानकारी खोज रहा हूँ...** (RAG)" normalized = rag_pipeline.normalize_jargon(text_input) eng_query = rag_pipeline.translate_to_retrieval_query(normalized) context_docs = rag_pipeline.retrieve_with_embeddings(eng_query, top_k=3) citations = "\n".join([f"• {doc['title']}" for doc in context_docs]) if context_docs else "" yield text_input, "", "", None, "🤖 **उत्तर तैयार कर रहा हूँ...** (LLM)" context = "\n\n".join( f"[{i+1}] {d['title']}\n{d['content'][:500]}" for i, d in enumerate(context_docs) ) if context_docs else "कोई प्रासंगिक जानकारी नहीं मिली।" full_prompt = ( f"नीचे दी गई जानकारी के आधार पर प्रश्न का उत्तर दें:\n\n" f"{context}\n\n" f"प्रश्न: {text_input}" ) answer = llm.llm_generate(full_prompt) yield text_input, answer, citations, None, "🔊 **आवाज़ बना रहा हूँ...** (TTS)" clean_text = rag_pipeline.format_response_for_tts(answer) audio_out = tts.tts_hindi(clean_text) yield text_input, answer, citations, audio_out, "✅ **पूरा हुआ**" except Exception as e: import traceback; traceback.print_exc() yield text_input, f"⚠️ त्रुटि: {e}", "", None, "❌ *विफल*" # ───────────────────────────────────────────── # GRADIO UI # ───────────────────────────────────────────── EXAMPLES = [ "मुझे होम लोन के लिए कौन से दस्तावेज़ चाहिए?", "EMI क्या होती है और कैसे calculate होती है?", "मुद्रा लोन कैसे मिलेगा?", "CIBIL score क्या होता है?", "PM Awas Yojana में subsidy कैसे मिलती है?", "जन धन खाता कैसे खोलें?", "किसान क्रेडिट कार्ड क्या है?", "Atal Pension Yojana में कैसे जुड़ें?", "SBI personal loan की interest rate क्या है?", "महिला स्वयं सहायता समूह को loan कैसे मिलेगा?", "Street vendor ko SVANidhi loan kaise milega?", "Bank ke khilaf complaint kahan karein?", ] CSS = """ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Devanagari:wght@300;400;500;700&family=Sora:wght@300;400;600;700&display=swap'); :root { --saffron: #FF6B00; --saffron-glow: rgba(255,107,0,0.15); --green: #0A7A3E; --navy: #0F1E3D; --navy-mid: #162848; --gold: #D4A017; --gold-light: #F0C042; --text-primary: #F4EFE6; --text-secondary: #B8C4D8; --border: rgba(212,160,23,0.3); } * { box-sizing: border-box; } body, .gradio-container { font-family: 'Sora','Noto Sans Devanagari',sans-serif !important; background: var(--navy) !important; color: var(--text-primary) !important; } .gradio-container { max-width: 1100px !important; margin: 0 auto !important; } .tricolor { height:4px; background:linear-gradient(90deg,#FF6B00 33%,white 33% 66%,#0A7A3E 66%); } .app-header { background: linear-gradient(135deg,var(--navy) 0%,var(--navy-mid) 60%,#0D2847 100%); border-bottom: 2px solid var(--gold); padding: 28px 40px 22px; position: relative; overflow: hidden; } .app-header::before { content:''; position:absolute; top:-60%; right:-5%; width:350px; height:350px; background:radial-gradient(circle,var(--saffron-glow) 0%,transparent 70%); pointer-events:none; } .app-header::after { content:'🏦'; position:absolute; right:36px; top:50%; transform:translateY(-50%); font-size:72px; opacity:0.09; } .header-badge { display:inline-flex; align-items:center; gap:8px; background:rgba(212,160,23,0.1); border:1px solid var(--gold); border-radius:100px; padding:4px 14px; font-size:11px; font-weight:600; letter-spacing:1.5px; text-transform:uppercase; color:var(--gold-light); margin-bottom:10px; } .header-title { font-size:28px; font-weight:700; color:var(--text-primary); margin-bottom:5px; font-family:'Noto Sans Devanagari','Sora',sans-serif !important; } .header-subtitle { font-size:13px; color:var(--text-secondary); font-weight:300; } .notice { background:rgba(212,160,23,0.07); border:1px solid rgba(212,160,23,0.28); border-radius:10px; padding:10px 18px; font-size:12px; color:var(--gold-light); margin:12px 40px; display:flex; align-items:flex-start; gap:10px; line-height:1.6; } .steps { display:flex; gap:6px; flex-wrap:wrap; padding:12px 40px; background:rgba(10,25,50,0.55); border-bottom:1px solid var(--border); } .step { display:flex; align-items:center; gap:5px; background:rgba(255,255,255,0.03); border:1px solid var(--border); border-radius:100px; padding:4px 11px; font-size:11px; color:var(--text-secondary); } .sn { width:16px; height:16px; background:var(--saffron); border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:9px; font-weight:700; color:white; flex-shrink:0; } label { color:var(--gold-light) !important; font-size:11px !important; font-weight:600 !important; letter-spacing:0.8px !important; text-transform:uppercase !important; } textarea { background:rgba(10,20,45,0.85) !important; border:1px solid var(--border) !important; border-radius:10px !important; color:var(--text-primary) !important; font-family:'Noto Sans Devanagari','Sora',sans-serif !important; font-size:14px !important; line-height:1.7 !important; padding:13px !important; } textarea:focus { border-color:var(--saffron) !important; outline:none !important; box-shadow:0 0 0 3px var(--saffron-glow) !important; } .answer-area textarea { background:linear-gradient(135deg,rgba(10,122,62,0.07) 0%,rgba(10,20,45,0.9) 100%) !important; border:1px solid rgba(10,122,62,0.3) !important; color:#DFF0E6 !important; font-size:15px !important; line-height:1.8 !important; min-height:130px !important; } .sources-area textarea { background:rgba(212,160,23,0.04) !important; border:1px solid rgba(212,160,23,0.18) !important; font-size:12px !important; color:var(--text-secondary) !important; } .panel { background:var(--navy-mid) !important; border:1px solid var(--border) !important; border-radius:14px !important; padding:20px !important; margin:8px !important; } button.primary { background:linear-gradient(135deg,var(--saffron) 0%,#D55000 100%) !important; border:none !important; border-radius:10px !important; color:white !important; font-weight:700 !important; font-size:13px !important; padding:12px 28px !important; cursor:pointer !important; transition:all 0.25s !important; box-shadow:0 4px 20px rgba(255,107,0,0.35) !important; letter-spacing:0.5px !important; text-transform:uppercase !important; } button.primary:hover { transform:translateY(-2px) !important; box-shadow:0 8px 30px rgba(255,107,0,0.5) !important; } .tab-nav button { background:transparent !important; border:none !important; border-bottom:2px solid transparent !important; color:var(--text-secondary) !important; font-size:13px !important; font-weight:500 !important; padding:10px 22px !important; border-radius:0 !important; transition:all 0.2s !important; } .tab-nav button.selected { border-bottom-color:var(--saffron) !important; color:#FF8C33 !important; font-weight:600 !important; } .footer { padding:16px 40px; text-align:center; font-size:11px; color:rgba(184,196,216,0.4); border-top:1px solid var(--border); background:rgba(8,15,35,0.5); } ::-webkit-scrollbar { width:5px; } ::-webkit-scrollbar-thumb { background:var(--gold); border-radius:3px; } """ def build_ui(): with gr.Blocks(css=CSS, title="बैंकिंग सहायक", theme=gr.themes.Base()) as demo: gr.HTML('
') gr.HTML("""HF_TOKEN
= your free HF token from hf.co/settings/tokens
माइक बटन दबाएं → हिंदी में बोलें → Submit
') v_audio_in = gr.Audio(sources=["microphone"], type="numpy", label="🎤 अपना प्रश्न बोलें") v_btn = gr.Button("🔍 उत्तर खोजें", variant="primary") v_transcript= gr.Textbox(label="📝 Transcript", interactive=False, lines=2, placeholder="आपकी बात यहाँ दिखेगी...") with gr.Column(scale=1, elem_classes="panel"): v_answer = gr.Textbox(label="💬 उत्तर", interactive=False, lines=5, placeholder="उत्तर यहाँ आएगा...", elem_classes="answer-area") v_audio_out = gr.Audio(label="🔊 उत्तर सुनें", type="numpy", autoplay=True) v_citations = gr.Textbox(label="📚 स्रोत", interactive=False, lines=3, elem_classes="sources-area") v_status = gr.Markdown("🔴 *तैयार*", elem_classes="status-bar") v_btn.click(fn=run_voice_pipeline, inputs=[v_audio_in], outputs=[v_transcript, v_answer, v_citations, v_audio_out, v_status]) with gr.Tab("⌨️ Text Mode"): with gr.Row(): with gr.Column(scale=1, elem_classes="panel"): gr.HTML('हिंदी या Hinglish में टाइप करें
') t_input = gr.Textbox(label="✏️ अपना प्रश्न लिखें", lines=3, placeholder="जैसे: होम लोन के लिए कौन से दस्तावेज़ चाहिए?") t_btn = gr.Button("🔍 उत्तर खोजें", variant="primary") t_transcript= gr.Textbox(label="📝 आपका प्रश्न", interactive=False, lines=2) with gr.Column(scale=1, elem_classes="panel"): t_answer = gr.Textbox(label="💬 उत्तर", interactive=False, lines=5, elem_classes="answer-area") t_audio_out = gr.Audio(label="🔊 उत्तर सुनें", type="numpy", autoplay=True) t_citations = gr.Textbox(label="📚 स्रोत", interactive=False, lines=3, elem_classes="sources-area") t_status = gr.Markdown("🔴 *तैयार*", elem_classes="status-bar") t_btn.click(fn=run_text_pipeline, inputs=[t_input], outputs=[t_transcript, t_answer, t_citations, t_audio_out, t_status]) t_input.submit(fn=run_text_pipeline, inputs=[t_input], outputs=[t_transcript, t_answer, t_citations, t_audio_out, t_status]) gr.HTML('📌 उदाहरण प्रश्न