Files
loyal_companion/cli/formatters.py
latte d957120eb3
All checks were successful
Enterprise AI Code Review / ai-review (pull_request) Successful in 38s
i forgot too commit
2026-02-01 15:57:45 +01:00

249 lines
7.5 KiB
Python

"""Terminal formatting for CLI responses."""
from datetime import datetime
from typing import Any
try:
from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
from rich.text import Text
RICH_AVAILABLE = True
except ImportError:
RICH_AVAILABLE = False
class ResponseFormatter:
"""Formats API responses for terminal display."""
def __init__(
self,
show_mood: bool = True,
show_relationship: bool = False,
show_facts: bool = False,
show_timestamps: bool = False,
use_rich: bool = True,
):
"""Initialize formatter.
Args:
show_mood: Show mood information
show_relationship: Show relationship information
show_facts: Show extracted facts
show_timestamps: Show timestamps
use_rich: Use rich formatting (if available)
"""
self.show_mood = show_mood
self.show_relationship = show_relationship
self.show_facts = show_facts
self.show_timestamps = show_timestamps
self.use_rich = use_rich and RICH_AVAILABLE
if self.use_rich:
self.console = Console()
def format_message(self, role: str, content: str, timestamp: str | None = None) -> str:
"""Format a chat message.
Args:
role: Message role (user/assistant)
content: Message content
timestamp: Optional timestamp
Returns:
str: Formatted message
"""
if self.use_rich:
return self._format_message_rich(role, content, timestamp)
return self._format_message_plain(role, content, timestamp)
def _format_message_plain(self, role: str, content: str, timestamp: str | None = None) -> str:
"""Format message in plain text.
Args:
role: Message role
content: Message content
timestamp: Optional timestamp
Returns:
str: Formatted message
"""
prefix = "You" if role == "user" else "Bartender"
lines = [f"{prefix}: {content}"]
if timestamp and self.show_timestamps:
try:
dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
time_str = dt.strftime("%H:%M:%S")
lines.append(f" [{time_str}]")
except Exception:
pass
return "\n".join(lines)
def _format_message_rich(self, role: str, content: str, timestamp: str | None = None) -> None:
"""Format message using rich.
Args:
role: Message role
content: Message content
timestamp: Optional timestamp
"""
if role == "user":
style = "bold cyan"
prefix = "You"
else:
style = "bold green"
prefix = "Bartender"
text = Text()
text.append(f"{prefix}: ", style=style)
text.append(content)
if timestamp and self.show_timestamps:
try:
dt = datetime.fromisoformat(timestamp.replace("Z", "+00:00"))
time_str = dt.strftime("%H:%M:%S")
text.append(f"\n [{time_str}]", style="dim")
except Exception:
pass
self.console.print(text)
def format_response(self, response: dict[str, Any]) -> str:
"""Format a chat response with metadata.
Args:
response: API response
Returns:
str: Formatted response
"""
if self.use_rich:
return self._format_response_rich(response)
return self._format_response_plain(response)
def _format_response_plain(self, response: dict[str, Any]) -> str:
"""Format response in plain text.
Args:
response: API response
Returns:
str: Formatted response
"""
lines = [f"Bartender: {response['response']}"]
# Add metadata
metadata = []
if self.show_mood and response.get("mood"):
mood = response["mood"]
metadata.append(f"Mood: {mood['label']}")
if self.show_relationship and response.get("relationship"):
rel = response["relationship"]
metadata.append(f"Relationship: {rel['level']} ({rel['score']})")
if self.show_facts and response.get("extracted_facts"):
facts = response["extracted_facts"]
if facts:
metadata.append(f"Facts learned: {len(facts)}")
if metadata:
lines.append(" " + " | ".join(metadata))
return "\n".join(lines)
def _format_response_rich(self, response: dict[str, Any]) -> None:
"""Format response using rich.
Args:
response: API response
"""
# Main response
text = Text()
text.append("Bartender: ", style="bold green")
text.append(response["response"])
self.console.print(text)
# Metadata panel
metadata_lines = []
if self.show_mood and response.get("mood"):
mood = response["mood"]
mood_line = Text()
mood_line.append("Mood: ", style="dim")
mood_line.append(mood["label"], style="yellow")
mood_line.append(
f" (v:{mood['valence']:.1f}, a:{mood['arousal']:.1f}, i:{mood['intensity']:.1f})",
style="dim",
)
metadata_lines.append(mood_line)
if self.show_relationship and response.get("relationship"):
rel = response["relationship"]
rel_line = Text()
rel_line.append("Relationship: ", style="dim")
rel_line.append(f"{rel['level']} ({rel['score']})", style="cyan")
rel_line.append(f" | {rel['interactions_count']} interactions", style="dim")
metadata_lines.append(rel_line)
if self.show_facts and response.get("extracted_facts"):
facts = response["extracted_facts"]
if facts:
facts_line = Text()
facts_line.append("Facts learned: ", style="dim")
facts_line.append(f"{len(facts)}", style="magenta")
metadata_lines.append(facts_line)
if metadata_lines:
self.console.print()
for line in metadata_lines:
self.console.print(" ", line)
def format_history_message(self, message: dict[str, Any]) -> str:
"""Format a history message.
Args:
message: History message
Returns:
str: Formatted message
"""
return self.format_message(message["role"], message["content"], message.get("timestamp"))
def print_error(self, message: str) -> None:
"""Print an error message.
Args:
message: Error message
"""
if self.use_rich:
self.console.print(f"[bold red]Error:[/bold red] {message}")
else:
print(f"Error: {message}")
def print_info(self, message: str) -> None:
"""Print an info message.
Args:
message: Info message
"""
if self.use_rich:
self.console.print(f"[dim]{message}[/dim]")
else:
print(message)
def print_success(self, message: str) -> None:
"""Print a success message.
Args:
message: Success message
"""
if self.use_rich:
self.console.print(f"[bold green]✓[/bold green] {message}")
else:
print(f"{message}")