Files
ryan a1e6b98ca9 Add screenshot to README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-27 10:07:43 -04:00

91 lines
3.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# bikeslop
A local web app for visualising Garmin cycling GPX files with AI coaching insights powered by a local LLM.
![bikeslop screenshot](static/bikeslop.png)
## Features
- **Route map** — Leaflet.js map with per-segment polylines coloured by heart-rate intensity zone
- **Elevation & HR charts** — Chart.js profiles plotted against cumulative distance
- **Stats sidebar** — distance, duration, speed, elevation gain, avg/max HR, HR zone breakdown (Karvonen)
- **AI coaching insights** — streaming post-ride feedback from a local LLM (via OpenAI-compatible API)
- **Garmin sync** — fetch new rides directly from Garmin Connect, including MFA support
- **Imperial/metric toggle** — persisted in `localStorage`
## Requirements
- Python 3.14+
- [uv](https://github.com/astral-sh/uv)
- A local OpenAI-compatible LLM server (e.g. [llama.cpp](https://github.com/ggerganov/llama.cpp), [Ollama](https://ollama.com))
## Setup
```bash
# 1. Clone and install dependencies
git clone https://git.torrtle.co/ryan/bikeslop
cd bikeslop
uv sync
# 2. Drop your GPX files into garmin_gpx_exports/
# (or use the in-app "Fetch from Garmin" button)
# 3. Start the server
LLAMACPP_BASE_URL=http://localhost:8080/v1 uv run uvicorn app:app --reload
```
Open http://localhost:8000 in your browser.
## Configuration
| Env var | Default | Description |
|---|---|---|
| `LLAMACPP_BASE_URL` | `http://localhost:8080/v1` | Base URL of your OpenAI-compatible LLM endpoint |
The model name is auto-discovered from `GET /v1/models` at startup. If the endpoint is unreachable, insights generation is still available — only that feature degrades.
## GPX files
Place `.gpx` files exported from Garmin Connect (or any GPX-compliant device) in the `garmin_gpx_exports/` directory. The app parses all files at startup. Supported data:
- Track points with `lat`/`lon`/`ele`/`time`
- Heart rate via the Garmin extension namespace (`ns3:hr`)
Activities with no track points (e.g. indoor rides) are listed with an "Indoor" badge and shown without a map or charts.
## Standalone GPX downloader
`main.py` is a standalone script for bulk-downloading your Garmin Connect cycling activities as GPX files without running the web server:
```bash
uv run python main.py
```
## API
| Method | Path | Description |
|---|---|---|
| `GET` | `/api/rides` | Summary list of all rides (no track points) |
| `GET` | `/api/rides/{id}` | Full ride metadata + cached insight |
| `GET` | `/api/rides/{id}/points` | Full lat/lon/ele/hr array |
| `POST` | `/api/rides/{id}/insights?rhr=60` | Stream AI coaching insight |
| `POST` | `/api/insights/generate-all?rhr=60` | SSE stream — generate insights for all rides |
| `POST` | `/api/fetch-rides` | SSE stream — fetch new rides from Garmin Connect |
| `POST` | `/api/mfa` | Submit MFA code for an in-progress Garmin session |
## HR zone boundaries (Karvonen)
Zones are calculated from your resting HR (configurable in the UI) and the lifetime max HR observed across all rides:
| Zone | % of HRR |
|---|---|
| Z1 | < 50% |
| Z2 | 5060% |
| Z3 | 6070% |
| Z4 | 7080% |
| Z5 | > 80% |
## License
MIT