agent_m/agentm/views/game_select.py
mscrnt 4500bfd388 Refactor agent management views and styles
- Removed commented-out header styles from styles.base.tcss and styles.tcss.
- Added new styles for danger buttons and agent selection views in styles.base.tcss and styles.tcss.
- Implemented AgentHomeView to manage agent actions and display metadata.
- Created AgentSelectView for selecting agents with a new layout and functionality.
- Added CreateAgentView for creating new agents with input validation.
- Removed obsolete eval.py and replaced it with evaluation.py.
- Developed GameSelectView for selecting games with a dynamic loading interface.
- Introduced ModelSelectView for selecting models associated with agents.
- Created SelectRunView for managing runs associated with agents.
- Added SubmissionView and TrainingView for handling model training and submission processes.
- Updated requirements.txt to include pyfiglet for ASCII art rendering.
2025-05-26 07:55:58 -07:00

228 lines
8.7 KiB
Python

from textual.screen import Screen
from textual.widgets import Static, Button
from textual.containers import Vertical, Horizontal, VerticalScroll, Grid
from textual.message import Message
from textual.reactive import reactive
from textual.widget import Widget
from rich.panel import Panel
from rich.console import Group
from rich.table import Table
from rich.rule import Rule
from rich.text import Text
from agentm.utils.logger import log_with_caller
from agentm.logic.roms import get_verified_roms, GAME_FILES
from agentm.theme.palette import get_theme
from agentm.components.footer import AgentMFooter
from .agent_select import AgentSelectView
palette = get_theme()
class GameSelected(Message):
def __init__(self, sender: Widget, metadata: dict):
self.metadata = metadata
super().__init__(sender)
class ProgressWidget(Widget):
message = reactive("Loading...")
def render(self) -> str:
return f"[bold {palette.ACCENT}]{self.message}[/]"
class GameCardButton(Button):
def __init__(self, metadata: dict, parent_view):
self.metadata = metadata
self.parent_view = parent_view
safe_id = metadata["rom_file"].replace(".", "_").replace("-", "_")
label = Text(metadata["title"], style=f"bold {palette.ACCENT}")
super().__init__(
label=label,
id=f"game_btn_{safe_id}",
classes="game_button"
)
self.styles.min_height = 3 # Ensures buttons stay visible even in constrained space
async def on_click(self):
await self.display_info()
self.parent_view.highlight_selected(self)
async def display_info(self):
meta = self.metadata
table = Table.grid(padding=(0, 1))
table.add_column("Key", style="bold underline", no_wrap=True)
table.add_column("Value", style=palette.ACCENT, overflow="fold")
table.add_row("Title", meta["title"])
table.add_row("Game ID", meta["game_id"])
table.add_row("Difficulty", f"{meta.get('difficulty_min')} - {meta.get('difficulty_max')}")
table.add_row("Characters", ", ".join(meta.get("characters", [])))
table.add_row("Keywords", ", ".join(meta.get("keywords", [])))
table.add_row("SHA256", meta["sha256"])
self.parent_view.shared_info_content.update(
Panel(Group(table, Rule(style="dim")), title="Game Info", border_style=palette.BORDER, expand=True)
)
self.parent_view.shared_confirm_button.label = f"✅ Confirm {meta['title']}"
self.parent_view.shared_confirm_button.disabled = False
self.parent_view.selected_game = meta
class HomeView(Screen):
BINDINGS = [
("escape", "app.quit", "Quit"),
("up", "scroll_up", "Scroll Up"),
("down", "scroll_down", "Scroll Down"),
("enter", "confirm_game", "Confirm Game"),
("r", "refresh_roms", "Refresh ROMs"),
]
def highlight_selected(self, selected_widget: GameCardButton):
for card in self.rom_grid.children:
if isinstance(card, GameCardButton):
card.remove_class("game_card_clicked")
selected_widget.add_class("game_card_clicked")
def compose(self):
self.logo = Static(
f"[bold {palette.ACCENT}]\n\n"
" █████╗ ██████╗ ███████╗ ███╗ ██╗ ████████╗ ███╗ ███╗\n"
"██╔══██╗ ██╔════╝ ██╔════╝ ████╗ ██║ ╚══██╔══╝ ████╗ ████║\n"
"███████║ ██║ ███╗ █████╗ ██╔██╗██║ ██║ ██╔████╔██║\n"
"██╔══██║ ██║ ██║ ██╔══╝ ██║╚████║ ██║ ██║╚██╔╝██║\n"
"██║ ██║ ╚██████╔╝ ███████╗ ██║ ╚███║ ██║ ██║ ██║\n"
"╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══╝ ╚═╝ ╚═╝ ╚═╝\n[/]",
classes="header",
expand=False,
)
self.welcome_text = Static(
"This is an unofficial DIAMBRA launcher to help you easily train, evaluate, and submit RL agents for fighting games.",
classes="body",
expand=False
)
self.progress_text = ProgressWidget()
self.loading_container = Vertical(
Static(f"[bold {palette.SUCCESS}]LOADING...[/]", expand=True),
self.progress_text,
id="loading_container"
)
self.dynamic_container = Vertical(self.loading_container, id="dynamic_content")
yield Vertical(
self.logo,
self.welcome_text,
self.dynamic_container,
AgentMFooter(compact=True),
id="home_screen_container",
classes="centered_layout"
)
async def on_mount(self):
log_with_caller("debug", "HomeView mounted. Starting ROM verification.")
self.selected_game = None
self.run_worker(self.run_verification, thread=True, exclusive=True, name="rom-verification")
def run_verification(self):
total = len(GAME_FILES)
verified_roms = get_verified_roms()
for idx, rom in enumerate(verified_roms, start=1):
self.app.call_from_thread(
lambda title=rom["title"], idx=idx: setattr(
self.progress_text, "message",
f"Processing {title} ({idx}/{total})"
)
)
import time
time.sleep(0.01)
self.app.call_from_thread(lambda: self.display_verified_roms(verified_roms))
async def display_verified_roms(self, verified_roms):
log_with_caller("info", f"ROM verification complete. Total: {len(verified_roms)}")
self.shared_info_content = Static(
Panel(
"[dim]Select a Game From the Grid Below to Start[/dim]",
title="Game Info",
border_style=palette.BORDER,
expand=True
),
id="info_panel_static"
)
self.shared_info_box = VerticalScroll(
self.shared_info_content,
id="game_info_box",
classes="game_info"
)
self.shared_info_box.styles.height = 7 # Around 5 visible rows
self.shared_confirm_button = Button(
"✅ Confirm",
id="confirm_button",
classes="confirm_button",
disabled=True
)
self.rom_grid = Grid(id="rom_grid", classes="rom_grid")
self.rom_grid.styles.grid_columns = ["1fr"] * 5
self.rom_grid.styles.grid_gap = (0, 1)
self.rom_grid.styles.width = "100%"
rom_grid_scroll = VerticalScroll(self.rom_grid, id="rom_grid_scroll")
rom_grid_scroll.styles.max_height = "30vh"
new_content = Vertical(
rom_grid_scroll,
Horizontal(
self.shared_info_box,
Vertical(self.shared_confirm_button, id="confirm_button_container"),
id="info_row",
classes="info_confirm_row"
),
id="game_content_layout",
classes="game_content_layout"
)
dynamic_container = self.query_one("#dynamic_content")
await dynamic_container.remove_children()
await dynamic_container.mount(new_content)
for rom in verified_roms:
await self.rom_grid.mount(GameCardButton(metadata=rom, parent_view=self))
async def on_button_pressed(self, event: Button.Pressed) -> None:
if event.button.id == "confirm_button" and self.selected_game:
await self.app.push_screen(AgentSelectView(self.selected_game))
async def action_scroll_up(self):
scroll = self.query_one("#rom_grid_scroll", VerticalScroll)
scroll.scroll_up()
async def action_scroll_down(self):
scroll = self.query_one("#rom_grid_scroll", VerticalScroll)
scroll.scroll_down()
async def action_confirm_game(self):
if self.selected_game:
log_with_caller("info", f"Keyboard: Confirming game {self.selected_game['title']}")
await self.app.push_screen(AgentSelectView(self.selected_game))
else:
log_with_caller("warning", "Keyboard: Tried to confirm with no game selected.")
async def action_refresh_roms(self):
log_with_caller("info", "Keyboard: Refreshing ROM list")
self.run_worker(self.run_verification, thread=True, exclusive=True, name="rom-verification")