"""Renderers for user and assistant chat messages."""
from __future__ import annotations
from rich.console import RenderableType
from rich.box import ROUNDED
from rich.markdown import Markdown
from rich.panel import Panel
from rich.text import Text
from clients.terminal.tui.themes import get_active_theme
from .base import ContentRenderer
from .markdown_content import ThemedMarkdownRenderable, _theme_aware_code_theme
def _hex_style(color) -> str:
"""Return a Rich-compatible hex style string from a Textual Color."""
return color.hex
class UserMessageRenderer(ContentRenderer):
"""Render a user message bubble."""
def accepts(self, msg: dict) -> bool:
return msg.get("type") == "user_message"
def render(self, msg: dict) -> RenderableType:
theme = get_active_theme()
text = msg.get("content", "")
return Panel(
Text(text, style=_hex_style(theme.text)),
title="You",
title_align="left",
border_style=_hex_style(theme.user_bubble),
box=ROUNDED,
)
class AssistantMessageRenderer(ContentRenderer):
"""Render an assistant response as a panel with markdown content.
The body is parsed as markdown (headings, lists, bold/italic, inline and
fenced code blocks with theme-aware syntax highlighting) using the same
themed renderer as the planning cards. Re-rendered on every stream delta
so the formatting appears live as Navi streams.
"""
def accepts(self, msg: dict) -> bool:
return msg.get("type") == "assistant_message"
def render(self, msg: dict) -> RenderableType:
theme = get_active_theme()
text = msg.get("content", "") or ""
code_theme = _theme_aware_code_theme(theme.name)
body = ThemedMarkdownRenderable(
Markdown(text, code_theme=code_theme, inline_code_theme=code_theme),
theme.name,
)
return Panel(
body,
title="Navi",
title_align="left",
border_style=_hex_style(theme.assistant_bubble),
box=ROUNDED,
)