"""Chat panel widget for the TUI."""
from __future__ import annotations
from rich.console import Group
from textual.app import ComposeResult
from textual.containers import ScrollableContainer
from textual.widgets import Static
from clients.terminal.tui.chat_model import ChatModel
from clients.terminal.tui.renderers import default_registry
class ChatPanel(ScrollableContainer):
"""Scrollable conversation panel."""
DEFAULT_CSS = """
ChatPanel {
border: solid $tui-border;
background: $tui-surface;
color: $tui-text;
padding: 0 1;
height: 1fr;
width: 2fr;
}
ChatPanel Static {
color: $tui-text;
}
"""
def __init__(self) -> None:
super().__init__()
self._model = ChatModel()
self._registry = default_registry()
self._items_container = Static("")
def compose(self) -> ComposeResult:
yield self._items_container
def on_mount(self) -> None:
self._items_container.styles.height = "auto"
def add_user_message(self, text: str) -> None:
self._model.add_user_message(text)
self._refresh()
def handle_ws_event(self, msg: dict) -> None:
self._model.handle_ws_event(msg)
self._refresh()
def _refresh(self) -> None:
renderables = []
for item in self._model.items:
if item.kind == "user_message":
renderables.append(self._registry.render({"type": "user_message", "content": item.content}))
elif item.kind == "assistant_message":
renderables.append(self._registry.render({"type": "assistant_message", "content": item.content}))
elif item.kind == "thinking_block":
renderables.append(self._registry.render({"type": "thinking_block", "content": item.content}))
elif item.kind == "tool_started":
renderables.append(self._registry.render({"type": "tool_started", **item.meta}))
elif item.kind == "tool_call":
renderables.append(self._registry.render({"type": "tool_call", **item.meta}))
elif item.kind == "error":
renderables.append(self._registry.render({"type": "error", "message": item.content}))
else:
renderables.append(self._registry.render({"type": "plain", "content": item.content}))
self._items_container.update(Group(*renderables))
self.scroll_end(animate=False)