CoolFace
Apppublic

Area51base/cc-chatbot

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
app.py264 linesDownload Raw Back to root
1"""2Hugging Face Spaces entry point — app.py3Runs as a native Streamlit app (sdk: streamlit).4Ingestion runs once at startup via st.cache_resource.5All queries go directly to the Python backend (no HTTP server needed).6"""7import os8import sys9import uuid10import json11import re12import html13from typing import Optional14 15import streamlit as st16import httpx17 18# ── Path setup ────────────────────────────────────────────────────────────────19sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))20 21os.environ.setdefault("CHROMA_PERSIST_DIR", "/tmp/chroma_db")22os.environ.setdefault("APP_ENV", "production")23os.environ.setdefault("CORS_ORIGINS", '["*"]')24 25API_BASE = os.getenv("CC_API_URL", "http://localhost:8000")26 27# ── One-time ingestion ────────────────────────────────────────────────────────28@st.cache_resource(show_spinner="⏳ Loading credit card data (first launch ~2 min)…")29def _bootstrap():30    """Run ingestion (if needed) then return the ask() function. Cached — runs only once."""31    chroma_dir = os.getenv("CHROMA_PERSIST_DIR", "/tmp/chroma_db")32    db_file = os.path.join(chroma_dir, "chroma.sqlite3")33    if not os.path.exists(db_file):34        import subprocess35        subprocess.run([sys.executable, "scripts/ingest.py"], check=False)36    # Verify DB actually has data — re-run if empty37    try:38        import chromadb39        from chromadb.config import Settings as ChromaSettings40        client = chromadb.PersistentClient(41            path=chroma_dir,42            settings=ChromaSettings(anonymized_telemetry=False),43        )44        col = client.get_or_create_collection("cc_cards")45        if col.count() == 0:46            import subprocess47            subprocess.run([sys.executable, "scripts/ingest.py"], check=False)48    except Exception:49        pass50    from cc_chatbot.services.orchestrator import ask51    return ask52 53_ask = _bootstrap()54 55# ── Page config ───────────────────────────────────────────────────────────────56st.set_page_config(57    page_title="CC AI Assistant",58    page_icon="💳",59    layout="centered",60    initial_sidebar_state="collapsed",61)62 63# ── Custom CSS ────────────────────────────────────────────────────────────────64st.markdown("""65<style>66.user-bubble {67    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);68    color: white;69    padding: 12px 16px;70    border-radius: 18px 18px 4px 18px;71    margin: 8px 0 8px 20%;72    max-width: 80%;73    float: right;74    clear: both;75    font-size: 0.95rem;76    box-shadow: 0 2px 8px rgba(0,0,0,0.15);77}78.assistant-bubble {79    background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf0 100%);80    color: #1a1a2e;81    padding: 14px 18px;82    border-radius: 18px 18px 18px 4px;83    margin: 8px 20% 8px 0;84    max-width: 80%;85    float: left;86    clear: both;87    font-size: 0.95rem;88    box-shadow: 0 2px 8px rgba(0,0,0,0.1);89    border-left: 3px solid #667eea;90}91.meta-badge {92    display: inline-block;93    background: #e8f4fd;94    color: #0366d6;95    padding: 2px 8px;96    border-radius: 10px;97    font-size: 0.75rem;98    margin: 2px;99}100.header-gradient {101    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);102    color: white;103    padding: 20px;104    border-radius: 12px;105    text-align: center;106    margin-bottom: 20px;107}108.clearfix { clear: both; }109[data-testid="stSidebar"] { display: none !important; }110[data-testid="collapsedControl"] { display: none !important; }111</style>112""", unsafe_allow_html=True)113 114# ── Session state ─────────────────────────────────────────────────────────────115if "session_id" not in st.session_state:116    st.session_state.session_id = str(uuid.uuid4())117if "messages" not in st.session_state:118    st.session_state.messages = []119if "suggestions" not in st.session_state:120    st.session_state.suggestions = []121 122# ── Header ────────────────────────────────────────────────────────────────────123col_h, col_btn = st.columns([5, 1])124with col_h:125    st.markdown("""126<div class="header-gradient">127    <h2 style="margin:0">💳 Credit Card AI Assistant</h2>128    <p style="margin:4px 0 0 0; opacity:0.9; font-size:0.9rem">129        Find the best cards · Compare rewards · Plan redemptions130    </p>131</div>132""", unsafe_allow_html=True)133with col_btn:134    st.markdown("<div style='padding-top:18px'></div>", unsafe_allow_html=True)135    if st.button("🔄 New", use_container_width=True):136        st.session_state.session_id = str(uuid.uuid4())137        st.session_state.messages = []138        st.session_state.suggestions = []139        st.rerun()140 141# ── Example questions ─────────────────────────────────────────────────────────142if not st.session_state.messages:143    st.markdown("**💡 Try asking:**")144    examples = [145        "Best card for Swiggy food orders?",146        "Which card for international travel?",147        "I have 20,000 HDFC Infinia points, want to go to Spain",148        "Compare HDFC Infinia vs Diners Club Black",149        "Best no-fee credit card with good rewards",150        "Which card gives unlimited lounge access?",151        "Best cashback card for Amazon shopping",152        "HDFC Regalia rewards and benefits",153    ]154    cols = st.columns(2)155    for i, eq in enumerate(examples):156        if cols[i % 2].button(eq, key=f"ex_{i}", use_container_width=True):157            st.session_state.pending_question = eq158 159 160# ── Helpers ───────────────────────────────────────────────────────────────────161def _format_answer(text: str) -> str:162    safe = html.escape(text)163    safe = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", safe)164    safe = re.sub(r"^(\d+)\.\s+", r"<br><strong>\1.</strong> ", safe, flags=re.MULTILINE)165    safe = re.sub(r"^✅\s+", r"<br>✅ ", safe, flags=re.MULTILINE)166    safe = re.sub(r"^[-•]\s+", r"<br>• ", safe, flags=re.MULTILINE)167    safe = re.sub(r"^(──.+)$", r"<br><strong>\1</strong>", safe, flags=re.MULTILINE)168    safe = re.sub(r"^(Verdict:)", r"<br><strong>Verdict:</strong>", safe, flags=re.MULTILINE)169    safe = re.sub(r"^(Pro tip:)", r"<br><strong>💡 Pro tip:</strong>", safe, flags=re.MULTILINE)170    safe = safe.replace("\n", "<br>")171    return safe172 173 174def render_message(role: str, content: str, meta: Optional[dict] = None):175    if role == "user":176        st.markdown(177            f'<div class="user-bubble">{content}</div><div class="clearfix"></div>',178            unsafe_allow_html=True,179        )180    else:181        badges = ""182        if meta:183            if meta.get("intent"):184                badges += f'<span class="meta-badge">🎯 {meta["intent"]}</span>'185            if meta.get("cache_hit"):186                badges += f'<span class="meta-badge">⚡ Cached ({meta.get("similarity", 0):.2f})</span>'187        st.markdown(188            f'<div class="assistant-bubble">{badges}<br>{_format_answer(content)}</div><div class="clearfix"></div>',189            unsafe_allow_html=True,190        )191 192 193# ── Render chat history ───────────────────────────────────────────────────────194for msg in st.session_state.messages:195    render_message(msg["role"], msg["content"], msg.get("meta"))196 197# ── Suggestions ───────────────────────────────────────────────────────────────198if st.session_state.suggestions:199    st.markdown("**💬 You might also ask:**")200    cols = st.columns(len(st.session_state.suggestions[:3]))201    for i, s in enumerate(st.session_state.suggestions[:3]):202        if cols[i].button(s, key=f"sug_{i}_{s[:20]}"):203            st.session_state.pending_question = s204 205if "pending_question" in st.session_state:206    st.session_state.pending_send = st.session_state.pop("pending_question")207 208# ── Chat input ────────────────────────────────────────────────────────────────209user_input = st.chat_input("Ask about credit cards — rewards, travel, dining, comparisons…")210question = user_input or st.session_state.pop("pending_send", None)211 212 213def _send(q: str):214    st.session_state.messages.append({"role": "user", "content": q})215    st.session_state.suggestions = []216    with st.spinner("Thinking…"):217        try:218            # Try HTTP API first (if local backend is running)219            resp = httpx.post(220                f"{API_BASE}/api/chat",221                json={"question": q, "session_id": st.session_state.session_id},222                timeout=60,223            )224            resp.raise_for_status()225            data = resp.json()226        except Exception:227            # Direct Python call (works on HF Spaces without a separate server)228            try:229                data = _ask(question=q, session_id=st.session_state.session_id)230            except Exception as e:231                err = str(e)232                if "list index out of range" in err or "empty" in err.lower():233                    answer = (234                        "⏳ **Still loading data** — the credit card database is being built "235                        "in the background (takes ~2 minutes on first launch). "236                        "Please try again in a moment!"237                    )238                else:239                    answer = f"❌ Error: {err}"240                st.session_state.messages.append({"role": "assistant", "content": answer, "meta": {}})241                return242 243    answer = data.get("answer", "Sorry, I couldn't generate an answer.")244    meta = {245        "intent": data.get("intent"),246        "cache_hit": data.get("cache_hit", False),247        "similarity": data.get("similarity", 0),248    }249    st.session_state.session_id = data.get("session_id", st.session_state.session_id)250    st.session_state.suggestions = data.get("suggestions", [])251    st.session_state.messages.append({"role": "assistant", "content": answer, "meta": meta})252 253 254if question:255    _send(question)256    st.rerun()257 258# ── Footer ────────────────────────────────────────────────────────────────────259st.markdown(260    "<hr><p style='text-align:center;color:#888;font-size:0.8rem'>"261    "💳 CC AI Assistant • Data from Livemint • Powered by Gemini</p>",262    unsafe_allow_html=True,263)264