feat: workout-suggester, weather-aware workouts from a local Gemma
Go + Templ + HTMX app that checks the weather (Open-Meteo, MET Norway fallback), scores run/ride/walk with plain rules, and has a local open-weight model (Gemma 4 E2B via any OpenAI-compatible server) write the plan. Routes are loops from your door (BRouter) plus signposted OpenStreetMap routes (Overpass), with GPX export.
This commit is contained in:
42 files changed
+7219
No files matched your search
@@ -0,0 +1,68 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.11"
|
||||
# dependencies = ["playwright"]
|
||||
# ///
|
||||
"""Browser end-to-end test: taps "📍 Here" (with a faked GPS fix), waits for
|
||||
the coach's streamed advice and the route map, asks a follow-up, and saves
|
||||
screenshots.
|
||||
|
||||
uv run scripts/e2e.py [--url http://127.0.0.1:3000] [--dark] [--mobile]
|
||||
"""
|
||||
import argparse, sys
|
||||
from playwright.sync_api import sync_playwright, expect
|
||||
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--url", default="http://127.0.0.1:3000")
|
||||
p.add_argument("--channel", default="msedge", help="installed browser: chrome, msedge, ...")
|
||||
p.add_argument("--out", default="docs")
|
||||
p.add_argument("--dark", action="store_true")
|
||||
p.add_argument("--mobile", action="store_true")
|
||||
p.add_argument("--lat", type=float, default=51.0543)
|
||||
p.add_argument("--lon", type=float, default=3.7174)
|
||||
args = p.parse_args()
|
||||
|
||||
name = "screenshot" + ("-mobile" if args.mobile else "") + ("-dark" if args.dark else "")
|
||||
viewport = {"width": 400, "height": 860} if args.mobile else {"width": 1000, "height": 900}
|
||||
|
||||
with sync_playwright() as pw:
|
||||
browser = pw.chromium.launch(channel=args.channel, headless=True)
|
||||
ctx = browser.new_context(
|
||||
viewport=viewport,
|
||||
color_scheme="dark" if args.dark else "light",
|
||||
geolocation={"latitude": args.lat, "longitude": args.lon},
|
||||
permissions=["geolocation"],
|
||||
device_scale_factor=2,
|
||||
)
|
||||
page = ctx.new_page()
|
||||
errors = []
|
||||
page.on("pageerror", lambda e: errors.append(str(e)))
|
||||
page.goto(args.url)
|
||||
page.evaluate("localStorage.clear()")
|
||||
page.reload()
|
||||
|
||||
expect(page.locator("#health .dot.ok")).to_be_visible(timeout=10_000)
|
||||
page.get_by_text("🔥 Hard").click()
|
||||
page.get_by_text("📍 Here").click() # locates and submits
|
||||
|
||||
expect(page.locator(".weather")).to_be_visible(timeout=20_000)
|
||||
expect(page.locator(".score.picked")).to_be_visible()
|
||||
expect(page.locator("#thread .markdown")).to_be_visible(timeout=180_000)
|
||||
if page.locator(".score.indoor.picked").count() == 0:
|
||||
expect(page.locator(".routes .map.leaflet-container")).to_be_visible(timeout=60_000)
|
||||
page.wait_for_timeout(1500) # tiles
|
||||
|
||||
page.screenshot(path=f"{args.out}/{name}.png", full_page=True)
|
||||
|
||||
page.get_by_text("⏱️ Only 30 min").click()
|
||||
expect(page.locator("#thread .markdown")).to_have_count(2, timeout=180_000)
|
||||
replies = page.locator("#thread .markdown").all_inner_texts()
|
||||
print("--- plan ---\n" + replies[0] + "\n--- follow-up ---\n" + replies[1])
|
||||
|
||||
page.reload() # the plan survives a reload
|
||||
expect(page.locator("#thread .markdown")).to_have_count(2)
|
||||
|
||||
browser.close()
|
||||
if errors:
|
||||
print("page errors:", errors, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f"ok: {args.out}/{name}.png")
|
||||
Reference in new issue
Block a user