mirror of
https://github.com/sstent/AICyclingCoach.git
synced 2025-12-05 23:52:06 +00:00
214 lines
6.9 KiB
Python
214 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AI Cycling Coach - CLI TUI Application
|
|
Entry point for the terminal-based cycling training coach.
|
|
"""
|
|
import asyncio
|
|
import logging
|
|
from pathlib import Path
|
|
import sys
|
|
from typing import Optional
|
|
|
|
from textual.app import App, ComposeResult
|
|
from textual.containers import Container, Horizontal, Vertical
|
|
from textual.widgets import (
|
|
Header, Footer, Static, Button, DataTable,
|
|
Placeholder, TabbedContent, TabPane
|
|
)
|
|
from textual.logging import TextualHandler
|
|
from textual import on
|
|
|
|
from backend.app.config import settings
|
|
from backend.app.database import init_db
|
|
# Use working dashboard with static content
|
|
from tui.views.dashboard_working import WorkingDashboardView as DashboardView
|
|
from tui.views.workouts import WorkoutView
|
|
from tui.views.plans import PlanView
|
|
from tui.views.rules import RuleView
|
|
from tui.views.routes import RouteView
|
|
|
|
|
|
class CyclingCoachApp(App):
|
|
"""Main TUI application for AI Cycling Coach."""
|
|
|
|
CSS = """
|
|
.title {
|
|
text-align: center;
|
|
color: $accent;
|
|
text-style: bold;
|
|
padding: 1;
|
|
}
|
|
|
|
.sidebar {
|
|
width: 20;
|
|
background: $surface;
|
|
}
|
|
|
|
.main-content {
|
|
background: $background;
|
|
}
|
|
|
|
.nav-button {
|
|
width: 100%;
|
|
height: 3;
|
|
margin: 1 0;
|
|
}
|
|
|
|
.nav-button.-active {
|
|
background: $accent;
|
|
color: $text;
|
|
}
|
|
|
|
TabbedContent {
|
|
height: 1fr;
|
|
width: 1fr;
|
|
}
|
|
|
|
TabPane {
|
|
height: 1fr;
|
|
width: 1fr;
|
|
}
|
|
"""
|
|
|
|
TITLE = "AI Cycling Coach"
|
|
SUB_TITLE = "Terminal Training Interface"
|
|
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.current_view = "dashboard"
|
|
self._setup_logging()
|
|
|
|
def _setup_logging(self):
|
|
"""Configure logging for the TUI application."""
|
|
# Create logs directory
|
|
logs_dir = Path("logs")
|
|
logs_dir.mkdir(exist_ok=True)
|
|
|
|
# Set up logger
|
|
logger = logging.getLogger("cycling_coach")
|
|
logger.setLevel(logging.INFO)
|
|
|
|
# Add Textual handler for TUI-compatible logging
|
|
textual_handler = TextualHandler()
|
|
logger.addHandler(textual_handler)
|
|
|
|
# Add file handler
|
|
file_handler = logging.FileHandler(logs_dir / "app.log")
|
|
file_handler.setFormatter(
|
|
logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
)
|
|
logger.addHandler(file_handler)
|
|
|
|
def compose(self) -> ComposeResult:
|
|
"""Create the main application layout."""
|
|
sys.stdout.write("CyclingCoachApp.compose: START\n")
|
|
yield Header()
|
|
|
|
with Container():
|
|
with Horizontal():
|
|
# Sidebar navigation
|
|
with Vertical(classes="sidebar"):
|
|
yield Static("Navigation", classes="title")
|
|
yield Button("Dashboard", id="nav-dashboard", classes="nav-button")
|
|
yield Button("Workouts", id="nav-workouts", classes="nav-button")
|
|
yield Button("Plans", id="nav-plans", classes="nav-button")
|
|
yield Button("Rules", id="nav-rules", classes="nav-button")
|
|
yield Button("Routes", id="nav-routes", classes="nav-button")
|
|
yield Button("Settings", id="nav-settings", classes="nav-button")
|
|
yield Button("Quit", id="nav-quit", classes="nav-button")
|
|
|
|
# Main content area
|
|
with Container(classes="main-content"):
|
|
with TabbedContent(id="main-tabs"):
|
|
with TabPane("Dashboard", id="dashboard-tab"):
|
|
yield DashboardView(id="dashboard-view")
|
|
|
|
with TabPane("Workouts", id="workouts-tab"):
|
|
yield WorkoutView(id="workout-view")
|
|
|
|
with TabPane("Plans", id="plans-tab"):
|
|
yield PlanView(id="plan-view")
|
|
|
|
with TabPane("Rules", id="rules-tab"):
|
|
yield RuleView(id="rule-view")
|
|
|
|
with TabPane("Routes", id="routes-tab"):
|
|
yield RouteView(id="route-view")
|
|
|
|
yield Footer()
|
|
sys.stdout.write("CyclingCoachApp.compose: END\n")
|
|
|
|
async def on_mount(self) -> None:
|
|
"""Initialize the application when mounted."""
|
|
# Set initial active navigation
|
|
self.query_one("#nav-dashboard").add_class("-active")
|
|
|
|
async def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
"""Handle navigation button presses."""
|
|
button_id = event.button.id
|
|
|
|
if button_id == "nav-quit":
|
|
self.exit()
|
|
return
|
|
|
|
# Handle navigation
|
|
nav_mapping = {
|
|
"nav-dashboard": "dashboard-tab",
|
|
"nav-workouts": "workouts-tab",
|
|
"nav-plans": "plans-tab",
|
|
"nav-rules": "rules-tab",
|
|
"nav-routes": "routes-tab",
|
|
}
|
|
|
|
if button_id in nav_mapping:
|
|
# Update active tab
|
|
tabs = self.query_one("#main-tabs")
|
|
tabs.active = nav_mapping[button_id]
|
|
|
|
# Update navigation button styles
|
|
for nav_button in self.query("Button"):
|
|
nav_button.remove_class("-active")
|
|
event.button.add_class("-active")
|
|
|
|
@on(TabbedContent.TabActivated)
|
|
async def on_tab_activated(self, event: TabbedContent.TabActivated) -> None:
|
|
sys.stdout.write(f"CyclingCoachApp.on_tab_activated: Tab {event.pane.id} activated\n")
|
|
"""Handle tab activation to load data for the active tab."""
|
|
if event.pane.id == "workouts-tab":
|
|
workout_view = self.query_one("#workout-view", WorkoutView)
|
|
sys.stdout.write("CyclingCoachApp.on_tab_activated: Calling workout_view.load_data()\n")
|
|
workout_view.load_data()
|
|
|
|
def action_quit(self) -> None:
|
|
self.exit()
|
|
|
|
async def init_db_async():
|
|
try:
|
|
await init_db()
|
|
sys.stdout.write("Database initialized successfully\n")
|
|
except Exception as e:
|
|
sys.stdout.write(f"Database initialization failed: {e}\n")
|
|
sys.exit(1)
|
|
|
|
def main():
|
|
"""Main entry point for the CLI application."""
|
|
# Create data directory if it doesn't exist
|
|
data_dir = Path("data")
|
|
data_dir.mkdir(exist_ok=True)
|
|
(data_dir / "gpx").mkdir(exist_ok=True)
|
|
(data_dir / "sessions").mkdir(exist_ok=True)
|
|
|
|
# Initialize database BEFORE starting the app
|
|
asyncio.run(init_db_async())
|
|
|
|
# Run the TUI application
|
|
sys.stdout.write("main(): Initializing CyclingCoachApp\n")
|
|
app = CyclingCoachApp()
|
|
sys.stdout.write("main(): CyclingCoachApp initialized. Running app.run()\n")
|
|
app.run()
|
|
sys.stdout.write("main(): app.run() finished.\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|