Generative UI for human performance — a Gemini-powered agent that reads your Apple Watch biometrics and generates a dynamic, contextual UI for your wellness state instead of a fixed dashboard.
Built for: AI Tinkerers Santiago — Generative UI Global Hackathon
Traditional health apps show static dashboards. This agent generates the UI dynamically based on your actual biometric state. The model analyzes your HRV, sleep, heart rate, and activity data, then picks which components to render and how to frame the insights.
Apple Watch → HealthKit → Gemini 2.5 Flash Lite → JSON widget spec → Flutter renders it
This is the A2UI pattern: agents return UI specs, not just text.
- Open app → agent reads your last 24h of biometrics
- Agent returns: wellness score + 5–8 dynamic widgets + 3 recommendations
- Chat mode: ask "¿estoy listo para entrenar hoy?" → agent responds with contextual widgets and can call tools (
show_insight,recommend_exercise, etc.) that slide overlays over the chat
- Flutter 3.41+ · Dart 3.11+
- Xcode 16+ (for iOS) and CocoaPods (
sudo gem install cocoapodsif missing) - A Google AI Studio API key (free tier is enough for the demo) — aistudio.google.com
git clone https://github.com/kaihv/wellness-hackathon
cd wellness-hackathon
flutter pub get
cd ios && pod install && cd ..The app reads its keys via --dart-define. The easiest way is to drop a .env at the repo root and let Flutter load it with --dart-define-from-file=.env. Required vs optional:
# ── REQUIRED ──────────────────────────────────────────────────────
GOOGLE_API_KEY=AIza... # Gemini — aistudio.google.com/app/apikey
# ── OPTIONAL ──────────────────────────────────────────────────────
STRAVA_CLIENT_ID= # developers.strava.com — for activity data
STRAVA_CLIENT_SECRET=
DEEPGRAM_API_KEY= # Realtime STT in chat input. If empty,
# falls back to on-device speech_to_text.
FIREBASE_API_KEY= # Vertex AI fallback when AI Studio quota
# is exhausted. Ask a teammate for the
# shared `makana-flutter-app` key. If
# empty, the agent stays on AI Studio
# only (free-tier quota applies).
WELLNESS_BACKEND_URL=http://localhost:8000 # AG-UI backend; defaults to
# localhost. If unreachable, the
# app calls Gemini directly..env is gitignored — never commit it.
Two equivalent options:
# A. Use the helper script (reads root .env or backend/.env, forwards keys)
bash scripts/run-flutter.sh
# B. Pass the file directly to flutter run
flutter run --dart-define-from-file=.envPin a specific device with -d <device-id> (flutter devices to list).
Most of the app works without a backend (Flutter calls Gemini directly). To showcase the full AG-UI + LangGraph + Daytona pipeline, start the Python backend:
cd backend
cp .env.example .env # add your keys
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python main.py # → http://localhost:8000Then run the Flutter app with WELLNESS_BACKEND_URL pointing at it (or use ngrok if running on a physical iPhone).
The default model is gemini-2.5-flash-lite (~1000 RPD on the free tier). If you switch to gemini-2.5-flash you'll hit RESOURCE_EXHAUSTED after 20 requests/day. To raise this, enable billing on your Google Cloud project tied to the API key.
- The repo's
ios/Runner/Info.plistalready declares the usage strings the runtime needs (NSHealth*,NSMicrophoneUsageDescription,NSSpeechRecognitionUsageDescription,NSMotionUsageDescription,NSLocalNetworkUsageDescription). Without these, iOS terminates the app with a TCC privacy violation when the chat opens. - The simulator falls back to
WellnessSnapshot.mock()for biometrics. For real HRV / sleep stages you need a physical iPhone paired with an Apple Watch.
lib/
├── main.dart # Entry + API key setup + onboarding gate
├── config.dart # GOOGLE_API_KEY / STRAVA_* / DEEPGRAM_* / model id
├── services/
│ ├── health_service.dart # HealthKit via `health` (+ mock fallback)
│ ├── strava_service.dart # Strava OAuth2 + activities API
│ ├── deepgram_realtime_service.dart # Deepgram WebSocket realtime STT
│ ├── speech_to_text_service.dart # On-device STT fallback (`es_CL`)
│ ├── ag_ui_service.dart # AG-UI Dart SDK to FastAPI backend (optional)
│ └── wellness_agent_service.dart # Gemini `generateContent` — chat() + analyze()
├── tools/
│ ├── wellness_tool.dart # 4 agent tools + Riverpod overlay state
│ └── wellness_tool_registry.dart # Function declarations for Gemini
└── ui/
├── theme/ # Brand kit (colors, typography, spaces, radii)
├── wellness_widget_factory.dart # JSON spec → widgets (6 types)
├── widgets/chat/ # chat_input_bar + stt_sheet + message_bubble
└── pages/
├── home_page.dart # Dashboard (analyze → factory)
├── agent_chat_page.dart # Conversational chat with tool overlays
└── connections/ # HealthKit / Strava / Garmin / Surfline pages
| Type | Description |
|---|---|
metric_card |
Single metric with trend + status color |
sleep_chart |
Stacked bar: deep / REM / light / awake |
activity_summary |
Strava activity with intensity badge |
insight |
Emoji + priority-colored insight card |
score_ring |
PieChart ring 0–100 |
progress_bar |
Goal progress (e.g. steps/day) |
| Metric | Source | Why it matters |
|---|---|---|
| HRV (SDNN) | Apple Watch | Primary recovery indicator |
| Resting HR | Apple Watch | Fitness + overtraining signal |
| Sleep stages | Apple Watch | Deep/REM quality |
| Steps | iPhone/Watch | Daily movement |
| Blood oxygen | Apple Watch | Altitude / breathing |
| Activities | Strava | Training load |
- Flutter 3.41+ (iOS-first, Android & web work)
- Gemini 2.5 Flash Lite via Google AI Studio
generateContent+ function calling - health ^13.3.1 — HealthKit / Google Fit
- speech_to_text ^7.0.0 + optional Deepgram realtime (WebSocket +
record) - ag_ui ^0.1.0 — AG-UI Dart SDK (CopilotKit sponsor) when backend is up
- fl_chart ^0.68.0 — charts
- flutter_riverpod ^2.5.1 + signals_flutter ^5.5.1 — state
- Strava API v3 — OAuth2 activities
- Backend (optional): FastAPI + LangGraph + LangSmith + Daytona
HTTP 429 RESOURCE_EXHAUSTEDin[Gemini]logs → free-tier quota hit. Wait until midnight Pacific or enable billing on the Cloud project.- iOS build fails on
record_linux→ runflutter pub getagain. The repo'sdependency_overridespinrecord_linux ^1.0.0to dodge a transitive break, and a stale lockfile can shadow it. - App crashes opening chat → an Info.plist is missing
NSSpeechRecognitionUsageDescription/NSMicrophoneUsageDescription. Already present in this repo, but if you regenerate the iOS folder, re-add them. - Chat replies "no pude conectar con el agente" → check the Dart console for a
[Gemini] HTTP …line. It surfaces the exact API error (bad key, model not found, quota, etc.).
MIT License — all IP belongs to the authors.
Built in 6 hours at AI Tinkerers Santiago, May 2026