Spaces:
Sleeping
Sleeping
| """ | |
| app.py | |
| ------ | |
| Streamlit frontend for the coaching recommendation system. | |
| """ | |
| import os | |
| import streamlit as st | |
| from setup_db import setup | |
| from retrieval import retrieve | |
| from reranker import rerank | |
| # ββ Page config βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.set_page_config( | |
| page_title="Coaching Assistant", | |
| page_icon="πͺπ»", | |
| layout="centered" | |
| ) | |
| # ββ One-time DB setup βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Sets up the SQLite DB if it doesn't already exist. | |
| if not os.path.exists("exercises.db"): | |
| with st.spinner("Setting up exercise database..."): | |
| setup() | |
| # ββ Header ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.title("πͺπ» Coaching Assistant") | |
| st.markdown( | |
| "Describe what you need and get personalised exercise recommendations. " | |
| "You can mention goals, injuries, equipment, or intensity." | |
| ) | |
| st.divider() | |
| # ββ Example queries βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown("**Try an example:**") | |
| examples = [ | |
| "I have knee pain and need low-impact exercises", | |
| "Explosive drills for a winger", | |
| "Upper body rehab, no weights", | |
| "Beginner core strengthening", | |
| ] | |
| cols = st.columns(len(examples)) | |
| for i, example in enumerate(examples): | |
| if cols[i].button(example, use_container_width=True): | |
| st.session_state["query"] = example | |
| # ββ Query input βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| query = st.text_input( | |
| label="Your query", | |
| placeholder="e.g. low-impact knee rehab for a beginner", | |
| key="query" | |
| ) | |
| search_clicked = st.button("Get Recommendations", type="primary", use_container_width=True) | |
| # ββ Pipeline ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| if search_clicked and query.strip(): | |
| with st.spinner("Finding the best exercises for you..."): | |
| # Stage 1: BM25 retrieval | |
| candidates = retrieve(query, top_k=15) | |
| if not candidates: | |
| st.warning("No matching exercises found. Try different keywords.") | |
| st.stop() | |
| # Stage 2: LLM re-ranking | |
| try: | |
| results = rerank(query, candidates, top_n=5) | |
| except Exception as e: | |
| st.error(f"Re-ranking failed: {e}") | |
| # Graceful fallback: show top BM25 results without re-ranking | |
| results = [{**c, "reason": ""} for c in candidates[:5]] | |
| # ββ Results βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.divider() | |
| st.subheader(f"Top recommendations for: *{query}*") | |
| st.caption(f"Retrieved {len(candidates)} candidates β re-ranked to top {len(results)}") | |
| for i, ex in enumerate(results, 1): | |
| # Difficulty badge colour | |
| diff_color = {"beginner": "π’", "intermediate": "π‘", "advanced": "π΄"}.get( | |
| ex.get("difficulty", "").lower(), "βͺ" | |
| ) | |
| with st.container(border=True): | |
| col1, col2 = st.columns([3, 1]) | |
| with col1: | |
| st.markdown(f"**{i}. {ex['title']}**") | |
| st.caption(ex.get("description", "")) | |
| with col2: | |
| st.markdown(f"{diff_color} {ex.get('difficulty', '').capitalize()}") | |
| st.caption(f"π· {ex.get('body_part', '').capitalize()}") | |
| if ex.get("reason"): | |
| st.info(f"π‘ {ex['reason']}") | |
| # Metadata pills | |
| meta_cols = st.columns(3) | |
| meta_cols[0].markdown(f"**Equipment:** {ex.get('equipment', 'none') or 'none'}") | |
| meta_cols[1].markdown(f"**Intensity:** {ex.get('intensity', 'β')}") | |
| meta_cols[2].markdown(f"**Focus:** {ex.get('injury_focus', 'β') or 'β'}") | |
| elif search_clicked and not query.strip(): | |
| st.warning("Please enter a query first.") | |
| # ββ Footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.divider() | |
| st.caption( | |
| "Pipeline: BM25 keyword retrieval β Mistral-7B re-ranking | " | |
| "Built with Streamlit Β· Deployed on HuggingFace Spaces Β· 100% free" | |
| ) | |