Newer
Older
navi-1 / clients / terminal / render.py
"""Terminal rendering helpers for Navi WebSocket events."""

from __future__ import annotations

import click


class Renderer:
    """Render stream events to the terminal."""

    def __init__(self, show_thinking: bool = False, show_events: bool = True) -> None:
        self.show_thinking = show_thinking
        self.show_events = show_events
        self._thinking_buffer: list[str] = []
        self._in_thinking = False

    def _print(self, text: str = "", *, color: str | None = None, nl: bool = True) -> None:
        click.secho(text, fg=color, nl=nl)

    def render(self, msg: dict) -> None:
        msg_type = msg.get("type")

        if msg_type == "heartbeat":
            return

        if msg_type == "session_sync":
            if self.show_events:
                profile_id = msg.get("profile_id") or "unknown"
                self._print(f"[session {msg.get('session_id')[:8]} | profile {profile_id}]", color="bright_black")
            return

        if msg_type == "stream_start":
            if self.show_events:
                self._print("[Navi] ", color="cyan", nl=False)
            return

        if msg_type == "thinking_delta":
            if self.show_thinking:
                self._thinking_buffer.append(msg.get("delta", ""))
            return

        if msg_type == "thinking_end":
            if self.show_thinking and self._thinking_buffer:
                self._print("\n[thinking]", color="bright_black")
                self._print("".join(self._thinking_buffer), color="bright_black")
                self._print("[/thinking]", color="bright_black")
                self._thinking_buffer.clear()
            return

        if msg_type == "stream_delta":
            self._print(msg.get("delta", ""), nl=False)
            return

        if msg_type == "tool_started":
            if self.show_events:
                tool = msg.get("tool", "?")
                args = msg.get("args") or {}
                self._print(f"\n[tool: {tool}]", color="yellow")
                if args:
                    self._print(str(args), color="bright_black")
            return

        if msg_type == "tool_call":
            if self.show_events:
                tool = msg.get("tool", "?")
                success = msg.get("success", True)
                color = "green" if success else "red"
                self._print(f"[tool result: {tool} success={success}]", color=color)
                result = msg.get("result")
                if result:
                    preview = str(result).replace("\n", " ")[:400]
                    self._print(preview, color="bright_black")
            return

        if msg_type == "stream_end":
            self._print()  # newline after response
            return

        if msg_type == "error":
            self._print(f"\n[error] {msg.get('message', 'unknown error')}", color="red")
            return

        if msg_type == "context_compressed":
            if self.show_events:
                self._print("[context compressed]", color="bright_black")
            return

        if self.show_events:
            self._print(f"[event: {msg_type}] {msg}", color="bright_black")