trainer_view.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. # -*- coding: utf-8 -*-
  2. """
  3. Trainer-View: Tabellarische Liste aller verfuegbaren Trainer.
  4. Zeigt Trainer in einer DataTable mit Name, Typ, Modell, Version.
  5. Enter/Klick auf eine Zeile oeffnet die Trainer-Detailansicht.
  6. """
  7. from __future__ import annotations
  8. from textual.app import ComposeResult
  9. from textual.widgets import Static, DataTable
  10. from trixy_core.tui.views.base import MainView
  11. class TrainerView(MainView):
  12. """
  13. F7: Trainer-Ansicht.
  14. Zeigt alle verfuegbaren ML-Trainer tabellarisch.
  15. Zeilen-Auswahl oeffnet Detail-Sub-Navigation.
  16. """
  17. TITLE = "Trainer"
  18. VIEW_ID = "trainer"
  19. def __init__(self, **kwargs) -> None:
  20. super().__init__(**kwargs)
  21. self._trainers: list[dict] = []
  22. self._refresh_counter: int = 0
  23. def compose(self) -> ComposeResult:
  24. yield Static("", id="trainer-header")
  25. yield DataTable(id="trainer-table", cursor_type="row")
  26. yield Static("", id="trainer-status")
  27. async def on_activate(self) -> None:
  28. """Laedt die Trainer-Liste."""
  29. table = self.query_one("#trainer-table", DataTable)
  30. if not table.columns:
  31. table.add_columns("Name", "Typ", "Modell-Typ", "Version", "Autor")
  32. await self._load_trainers()
  33. async def refresh_data(self) -> None:
  34. """Periodisches Update (alle 20 Sekunden)."""
  35. self._refresh_counter += 1
  36. if self._refresh_counter % 10 == 0:
  37. await self._load_trainers()
  38. async def _load_trainers(self) -> None:
  39. """Laedt die Trainer-Liste vom Server."""
  40. if not self.connection:
  41. return
  42. try:
  43. response = await self.connection.request_trainers()
  44. except Exception:
  45. self._set_status("Fehler beim Laden der Trainer", error=True)
  46. return
  47. if response is None:
  48. return
  49. self._trainers = response.trainers
  50. self._update_header()
  51. self._update_table()
  52. def _update_header(self) -> None:
  53. """Aktualisiert die Kopfzeile."""
  54. header = self.query_one("#trainer-header", Static)
  55. total = len(self._trainers)
  56. core = sum(1 for t in self._trainers if t.get("trainer_type") == "core")
  57. plugin = total - core
  58. header.update(f"[bold]Trainer:[/bold] {total} verfuegbar ({core} Core, {plugin} Plugin)")
  59. def _update_table(self) -> None:
  60. """Aktualisiert die Trainer-Tabelle."""
  61. table = self.query_one("#trainer-table", DataTable)
  62. table.clear()
  63. for trainer in sorted(self._trainers, key=lambda t: t.get("name", "")):
  64. name = trainer.get("name", "")
  65. trainer_type = trainer.get("trainer_type", "")
  66. model_type = trainer.get("model_type", "")
  67. version = trainer.get("version", "")
  68. author = trainer.get("author", "")
  69. type_label = "[cyan]Core[/cyan]" if trainer_type == "core" else "[green]Plugin[/green]"
  70. table.add_row(
  71. name, type_label, model_type, version, author,
  72. key=trainer.get("trainer_id", name),
  73. )
  74. async def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
  75. """Enter auf einer Zeile: Oeffnet die Detail-Ansicht."""
  76. row_key = str(event.row_key.value) if event.row_key else ""
  77. trainer_data = None
  78. for t in self._trainers:
  79. if t.get("trainer_id") == row_key:
  80. trainer_data = t
  81. break
  82. if not trainer_data:
  83. return
  84. try:
  85. app = self.app
  86. if hasattr(app, "open_trainer_detail"):
  87. await app.open_trainer_detail(trainer_data)
  88. except Exception:
  89. pass
  90. def _set_status(self, text: str, error: bool = False) -> None:
  91. """Setzt die Statuszeile."""
  92. status = self.query_one("#trainer-status", Static)
  93. if error:
  94. status.update(f"[red]{text}[/red]")
  95. else:
  96. status.update(f"[green]{text}[/green]")