--- title: Creighton Anatomy Learning Platform colorFrom: blue colorTo: blue sdk: gradio sdk_version: 5.49.1 python_version: "3.10" app_file: app.py pinned: false --- # Creighton Anatomy Learning Platform A chat-based anatomy tutor grounded in the course lecture slides. Answers, quizzes, and clinical correlations cite the lecture and slide they come from, and the slide viewer renders the cited pages directly from the lecture PDFs. - Students sign in with a user ID and email (new users create an account with the same two fields). Accounts, every chat message, time spent on the site, and every button click are stored in Supabase, and a returning student sees their earlier questions and answers again. - Every request is answered from the slides first. When the slides do not cover it, the tutor looks the topic up on [TeachMeAnatomy](https://teachmeanatomy.info/) and [Kenhub](https://www.kenhub.com/en/library/education/the-human-anatomy) and answers from those articles instead. Every answer ends with a **Source** line naming the slides or the article pages it came from. The tutor never answers from its own knowledge. - Articles are looked up per anatomical structure: a question about the knee and the ankle gets the knee joint and ankle joint articles, and a page is never chosen because of a word such as "injury", "fracture", or "layers" alone. - **Clinical Correlation** has two sections, each citing its source: "From the lecture slides" and "From TeachMeAnatomy" / "From Kenhub" / "From TeachMeAnatomy and Kenhub" (the clinical sections of the articles about the topic's structures). - While the tutor works, the chat shows a "Thinking" message with a spinner that reports each step. - Answers are written on a background thread, so an answer is finished and saved even when the student's connection drops while it is being written. When the connection comes back, the page reloads by itself, signs the student back in, and shows the chat with that answer; the question being typed is kept. After a Space restart the open page signs back in the same way. The sign-in is remembered only by that browser tab (`sessionStorage`) and used only for that automatic reload: a manual reload, a new tab or window, or a closed tab reopened by the next person on a shared computer starts at the sign-in form, and **Sign out** forgets it. - The viewer shows only the slides an answer actually rests on: slides cited in the answer and the figure slides that follow them. ## Files - `app.py` - the application. - `supabase_store.py` - accounts, chat history, sessions, and click events in Supabase (standard library only). - `web_sources.py` - looks up reference articles on TeachMeAnatomy and Kenhub through their sitemaps. - `supabase_schema.sql` - the tables and analytics views; run once in Supabase. - `verify_supabase.py` - checks a Supabase project end to end with a throwaway account. - `test_app.py` - offline tests (Gemini, Supabase, and the web are stubbed). - `requirements.txt` - Python dependencies (Gradio comes from the Space SDK). - `Lectures/` - the lecture PDFs (`Lecture #N - Title.pdf`) and the logo (`creighton-bluejays@logotyp.us.png`, or `resize.webp` as a fallback) shown at the bottom of the page. Copy the folder here as-is; it is read at startup. Uploading the folder zipped as `Lectures.zip` next to `app.py` also works: the app unpacks it on first start. ## Set up Supabase (once) 1. In your Supabase project open **SQL Editor**, paste the contents of `supabase_schema.sql`, and run it. It creates `anatomy_users`, `anatomy_messages`, `anatomy_sessions`, `anatomy_events`, and two views. 2. Copy the project URL and the **service_role** key from **Project Settings > API**. The app runs server-side, so the key never reaches a browser; the tables have Row Level Security on with no policies, so the public anon key can read nothing. 3. Check the wiring: ```bash export SUPABASE_URL="https://YOUR-PROJECT-REF.supabase.co" export SUPABASE_KEY="YOUR-SERVICE-ROLE-KEY" python3 verify_supabase.py ``` ### Analytics - `anatomy_time_per_user` - sessions and seconds on the site per user (the open tab reports every 30 s, so a session's length is `last_seen_at - started_at`). When a tab loses its connection (a laptop going to sleep), its session is closed; when the student comes back a new session starts, so the time away is not counted. - `anatomy_button_clicks` - clicks per user and button (`ask`, `quiz`, `clinical`, `simplify`, `show_image`, `clear`, `previous`, `next`, `browse_lecture`, `browse_slide`, `example`, `sign_in`, `create_account`, `sign_out`), plus `resume` when a page signed the student back in by itself after a reload or a Space restart. - `anatomy_events` holds the raw click log with a `detail` JSON column (question length, lecture and slide numbers); `anatomy_messages` holds every question and answer with its `source` (`slides`, `web`, `slides+web` for a clinical correlation from both, `none`, `error`). ## Deploy to Hugging Face Spaces 1. Create a new Space with the Gradio SDK. 2. In the Space settings, add these secrets: `GEMINI_API_KEY` (your Gemini API key), `SUPABASE_URL`, and `SUPABASE_KEY` (the service_role key). Optional: a variable `GEMINI_MODEL` to change the model (default `gemini-2.5-flash`; the app falls back to `gemini-3.8-flash` and `gemini-3.5-flash` when the configured model is not available to the key). 3. Copy the `Lectures` folder (or `Lectures.zip`) into this directory, then upload everything: ```bash hf upload / . . --repo-type space --exclude "__pycache__/*" --exclude ".DS_Store" --exclude "**/.DS_Store" ``` PDFs larger than 10 MB are stored with Git LFS automatically. The Space indexes the slides when it starts, which takes well under a minute; the reference-site sitemaps are downloaded in the background on startup and refreshed daily. ## Run locally ```bash pip install -r requirements.txt gradio==5.49.1 export GEMINI_API_KEY=your_key export SUPABASE_URL=https://YOUR-PROJECT-REF.supabase.co export SUPABASE_KEY=YOUR-SERVICE-ROLE-KEY python app.py ``` Set `LECTURES_DIR` to point at the lecture folder if it is not next to `app.py`. Run the offline tests with: ```bash LECTURES_DIR=/path/to/Lectures GEMINI_API_KEY=test python -m unittest -v test_app ```