158 lines
4.2 KiB
Python
158 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from textual.app import ComposeResult
|
|
from textual.containers import Vertical
|
|
from textual.screen import Screen
|
|
from textual.widgets import Button, Input, Static
|
|
from rich.markdown import Markdown as RichMarkdown
|
|
|
|
from run_utils import (
|
|
CHAR_PATH, TODAY, REFRESH_SECS,
|
|
clear_llm_log, ensure_log, read_todo, read_log_tail,
|
|
status_summary, log_count,
|
|
)
|
|
from run_ambience import HAS_AUDIO
|
|
|
|
|
|
# module-level ref filled by ChaosTUI
|
|
app_ambience_player: object | None = None
|
|
|
|
|
|
class RollModal(Screen):
|
|
CSS = """
|
|
RollModal {
|
|
align: center middle;
|
|
background: rgba(0, 0, 0, 0.75);
|
|
}
|
|
#roll-dialog {
|
|
width: 44;
|
|
height: auto;
|
|
padding: 2 3;
|
|
background: #2a2a3a;
|
|
border: thick #e0ad4c;
|
|
}
|
|
#roll-title {
|
|
text-style: bold;
|
|
color: #ffd93d;
|
|
text-align: center;
|
|
height: 3;
|
|
}
|
|
#roll-reason {
|
|
color: #c0b090;
|
|
text-align: center;
|
|
height: 3;
|
|
}
|
|
#roll-input {
|
|
margin: 1 0;
|
|
}
|
|
#roll-submit {
|
|
width: 100%;
|
|
}
|
|
#roll-hint {
|
|
color: #888888;
|
|
text-align: center;
|
|
height: 1;
|
|
}
|
|
"""
|
|
|
|
def __init__(self, dice: str, reason: str) -> None:
|
|
super().__init__()
|
|
self.dice = dice
|
|
self.reason = reason
|
|
|
|
def compose(self) -> ComposeResult:
|
|
with Vertical(id="roll-dialog"):
|
|
yield Static(f"[bold]🎲 ROLL {self.dice}[/bold]", id="roll-title")
|
|
yield Static(f"Reason: {self.reason}", id="roll-reason")
|
|
yield Input(placeholder="Enter the number you rolled...", id="roll-input")
|
|
yield Button("Submit", id="roll-submit", variant="primary")
|
|
yield Static("(or press Enter)", id="roll-hint")
|
|
|
|
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
self._submit(event.value)
|
|
|
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
if event.button.id == "roll-submit":
|
|
self._submit(self.query_one("#roll-input", Input).value)
|
|
|
|
def _submit(self, value: str) -> None:
|
|
val = value.strip()
|
|
if val:
|
|
self.dismiss(val)
|
|
|
|
|
|
class AutoStatic(Static):
|
|
def load(self):
|
|
raise NotImplementedError
|
|
|
|
def on_mount(self):
|
|
clear_llm_log()
|
|
ensure_log()
|
|
self.load()
|
|
self.set_interval(REFRESH_SECS, self.load)
|
|
|
|
|
|
class TodoPane(AutoStatic):
|
|
def load(self):
|
|
items = read_todo()
|
|
self.update("\n".join(f" ☐ {i}" for i in items))
|
|
|
|
|
|
class TranscriptPane(AutoStatic):
|
|
def load(self):
|
|
lines = read_log_tail()
|
|
display = "\n".join(lines[-80:])
|
|
if lines:
|
|
display += "\n\n>>--- NOW --->"
|
|
self.update(display)
|
|
self.call_after_refresh(self._scroll_bottom)
|
|
|
|
def _scroll_bottom(self):
|
|
if self.parent and hasattr(self.parent, 'scroll_end'):
|
|
self.parent.scroll_end(animate=False)
|
|
|
|
|
|
class DebugPane(Static):
|
|
MAX_LINES = 200
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self._lines: list[str] = []
|
|
|
|
def append(self, text: str) -> None:
|
|
self._lines.append(text)
|
|
if len(self._lines) > self.MAX_LINES:
|
|
self._lines.pop(0)
|
|
self.update("\n".join(self._lines[-100:]))
|
|
self.call_after_refresh(self._scroll_bottom)
|
|
|
|
def _scroll_bottom(self):
|
|
if self.parent and hasattr(self.parent, 'scroll_end'):
|
|
self.parent.scroll_end(animate=False)
|
|
|
|
def clear(self) -> None:
|
|
self._lines.clear()
|
|
self.update("")
|
|
|
|
|
|
class CharPane(AutoStatic):
|
|
def load(self):
|
|
if not CHAR_PATH.exists():
|
|
self.update("*No character sheet*")
|
|
return
|
|
self.update(RichMarkdown(CHAR_PATH.read_text().strip()))
|
|
|
|
|
|
class StatusBar(AutoStatic):
|
|
def load(self):
|
|
char = status_summary()
|
|
count = log_count()
|
|
todo = len(read_todo())
|
|
music = ""
|
|
if not HAS_AUDIO:
|
|
music = " │ ♫ (install miniaudio)"
|
|
elif app_ambience_player:
|
|
name = app_ambience_player.ambience_name
|
|
music = f" │ ♫ {name}"
|
|
self.update(f"{char} │ {count} entries │ {todo} todo │ {TODAY}{music}")
|