diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 11fa15c..181ae3d 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -2,6 +2,14 @@
FitMop is a full-stack fitness analytics and workout planning platform. It follows a decoupled Client-Server architecture with a FastAPI backend and a Vue.js frontend, orchestrated by a single startup script.
+## Core Technology Stack & Tooling
+
+> [!IMPORTANT]
+> **Adherence to these tools is mandatory.**
+> - **Backend Package Manager**: [`uv`](https://github.com/astral-sh/uv) (do NOT use pip directly).
+> - **AI SDK**: [`google-genai`](https://pypi.org/project/google-genai/) (Standard Python SDK for Gemini).
+> - **Frontend Tooling**: `Vite` + `npm`.
+
## System Overview
```mermaid
@@ -124,4 +132,4 @@ sequenceDiagram
## Security & Reliability
- **CORS**: Restricted to localhost:5173.
- **Error Handling**: Global FastAPI handler ensures API never crashes silently.
-- **Pre-flight**: `fitmop.sh` checks for mandatory environment variables before launch.
+- **Pre-flight**: `Makefile` checks for mandatory environment variables before launch.
diff --git a/GEMINI.md b/GEMINI.md
index b8cffed..87694b7 100644
--- a/GEMINI.md
+++ b/GEMINI.md
@@ -3,7 +3,7 @@
This document provides a set of global instructions and principles for the Gemini CLI to follow during our interactions.
**Reference:**
-- **[Project Architecture](file:///Users/moritz/src/fitness_antigravity/ARCHITECTURE.md)**: ALWAYS refer to this document for the technical layout and data flows of the system.
+- **[Project Architecture](file:///Users/moritz/src/fitness_antigravity/ARCHITECTURE.md)**: CRITICAL: You MUST read this file at the start of every session to understand the enforced tooling (`uv`, `google-genai`) and architectural patterns.
## Environment Management
- **Startup Rule:** ALWAYS start the application using `make run`. NEVER try to start individual services manually or use the old `fitmop.sh`.
diff --git a/backend/src/common/env_manager.py b/backend/src/common/env_manager.py
index 2e1b90c..a610338 100644
--- a/backend/src/common/env_manager.py
+++ b/backend/src/common/env_manager.py
@@ -1,5 +1,5 @@
import os
-from typing import Any, Dict
+from typing import Any, Dict, List
from dotenv import load_dotenv, set_key
@@ -34,7 +34,7 @@ class EnvManager:
# Reload after setting
self.load_service_env(service, override=True)
- def get_status(self, service: str, required_keys: list[str]) -> Dict[str, Any]:
+ def get_status(self, service: str, required_keys: List[str]) -> Dict[str, Any]:
"""Check if required keys are set for a service."""
# Reload just in case
self.load_service_env(service)
diff --git a/backend/src/recommendations/engine.py b/backend/src/recommendations/engine.py
index ed06ab6..285de62 100644
--- a/backend/src/recommendations/engine.py
+++ b/backend/src/recommendations/engine.py
@@ -89,8 +89,19 @@ class RecommendationEngine:
Validation Rules:
- SportTypes: RUNNING=1, CYCLING=2
- StepTypes: WARMUP=1, COOLDOWN=2, INTERVAL=3, RECOVERY=4, REST=5, REPEAT=6
- - EndCondition: DISTANCE=1, TIME=2, LAP_BUTTON=7
+ - EndCondition: DISTANCE=1, TIME=2, LAP_BUTTON=7, ITERATIONS=7
- TargetType: NO_TARGET=1, HEART_RATE=2, PACE=4 (Speed)
+
+ CRITICAL SCHEMA RULE:
+ Steps MUST use nested objects for types. Do NOT use flat IDs.
+ Example Step:
+ {
+ "type": "ExecutableStepDTO",
+ "stepOrder": 1,
+ "stepType": { "stepTypeId": 1, "stepTypeKey": "warmup" },
+ "endCondition": { "conditionTypeId": 2, "conditionTypeKey": "time" },
+ "endConditionValue": 600
+ }
"""
user_prompt = f"User Request: {prompt}"
diff --git a/backend/src/test_ai_modifier.py b/backend/src/test_ai_modifier.py
new file mode 100644
index 0000000..920e2be
--- /dev/null
+++ b/backend/src/test_ai_modifier.py
@@ -0,0 +1,102 @@
+
+import json
+import os
+import sys
+from typing import Dict, Any
+
+# Ensure we can import from src
+sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+
+from common.env_manager import EnvManager
+from garmin.workout_manager import WorkoutManager
+
+def load_sample_workout() -> Dict[str, Any]:
+ """Create a minimal valid workout for testing."""
+ return {
+ "workoutName": "Test Workout",
+ "description": "Base for AI test",
+ "sportType": {
+ "sportTypeId": 1,
+ "sportTypeKey": "running"
+ },
+ "workoutSegments": [
+ {
+ "segmentOrder": 1,
+ "sportType": {
+ "sportTypeId": 1,
+ "sportTypeKey": "running"
+ },
+ "workoutSteps": [
+ {
+ "type": "ExecutableStepDTO",
+ "stepOrder": 1,
+ "stepTypeId": 1,
+ "childStepId": None,
+ "endConditionId": 2,
+ "endConditionValue": 300,
+ "targetTypeId": 1,
+ "targetValueOne": 10.0,
+ "targetValueTwo": 12.0
+ }
+ ]
+ }
+ ]
+ }
+
+def test_ai_modification():
+ print("\n--- Testing AI Workout Modifier ---")
+
+ # 1. Setup Environment
+ root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
+ env = EnvManager(root_dir)
+ env.load_service_env("gemini")
+
+ api_key = os.getenv("GEMINI_API_KEY")
+ if not api_key:
+ print("SKIP: GEMINI_API_KEY not found in environment.")
+ return
+
+ # 2. Prepare Data
+ manager = WorkoutManager()
+ original_workout = load_sample_workout()
+
+ # 3. specific instruction
+ prompt = "Add a 10 minute cooldown at the end."
+ print(f"Prompt: {prompt}")
+
+ # 4. Execute AI Modification
+ try:
+ modified_workout = manager.generate_workout_json(prompt, existing_workout=original_workout)
+
+ # 5. Verify Structure (Schema Validation)
+ errors = manager.validate_workout_json(modified_workout)
+ if errors:
+ print(f"FAIL: Schema Validation Errors: {json.dumps(errors, indent=2)}")
+ print(f"Invalid Workout JSON: {json.dumps(modified_workout, indent=2)}")
+ return
+
+ # 6. Verify Content Logic
+ segments = modified_workout.get("workoutSegments", [])
+ if not segments:
+ print("FAIL: No segments found in modified workout.")
+ return
+
+ steps = segments[0].get("workoutSteps", [])
+ last_step = steps[-1] if steps else None
+
+ # Check if last step looks like a cooldown
+ # stepTypeId 4 = Cooldown (usually, or we check description/type)
+ is_cooldown = last_step and (last_step.get("stepTypeId") == 4 or "cool" in str(last_step).lower())
+
+ if is_cooldown:
+ print("PASS: Cooldown added successfully.")
+ print(f"Modified Steps Count: {len(steps)}")
+ else:
+ print("WARN: Could not strictly verify cooldown type, but schema is valid.")
+ print(f"Last Step: {json.dumps(last_step, indent=2)}")
+
+ except Exception as e:
+ print(f"FAIL: Exception during AI processing: {e}")
+
+if __name__ == "__main__":
+ test_ai_modification()
diff --git a/frontend/src/components/AiChatModal.vue b/frontend/src/components/AiChatModal.vue
new file mode 100644
index 0000000..3f8b10e
--- /dev/null
+++ b/frontend/src/components/AiChatModal.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
AI Workout Enhancer
+
+
+
+
+
+
+
+ Tell the AI how you want to modify {{ workout?.workoutName }}.
+ For example: "Add 3 intervals of 1 minute fast", "Make it a recovery run", or "Add a 15 min warmup".
+