Coverage for src / osiris_cli / enhanced_display.py: 0%
138 statements
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
« prev ^ index » next coverage.py v7.13.0, created at 2025-12-27 17:41 +0200
1#!/usr/bin/env python3
2"""
3Enhanced Display System for Osiris CLI v5.1
4Blends Gemini CLI's clean streaming with Letta Code's structured output
5"""
7import asyncio
8from typing import Optional, AsyncIterator
9from rich.console import Console
10from rich.live import Live
11from rich.spinner import Spinner
12from rich.panel import Panel
13from rich.text import Text
14from rich.table import Table
15from rich.markdown import Markdown
16import time
18console = Console()
21class StreamingDisplay:
22 """
23 Handles real-time streaming display like Gemini CLI
24 """
26 def __init__(self):
27 self.current_line = ""
28 self.buffer = []
30 async def stream_text(self, text: str, delay: float = 0.01):
31 """Stream text character-by-character"""
32 for char in text:
33 console.print(char, end="", markup=False)
34 await asyncio.sleep(delay)
35 console.print() # Newline at end
37 async def stream_markdown(self, text: str, delay: float = 0.01):
38 """Stream markdown with syntax highlighting"""
39 # For now, just stream as text
40 # TODO: Implement incremental markdown rendering
41 await self.stream_text(text, delay)
44class ToolDisplay:
45 """
46 Clean tool execution display (Gemini-inspired)
47 """
49 @staticmethod
50 def show_tool_call(tool_name: str, args: dict, compact: bool = True):
51 """Display tool being called"""
52 if compact:
53 # Compact format (Gemini-style)
54 args_str = ", ".join(f"{k}={repr(v)[:50]}" for k, v in args.items())
55 console.print(f"\n[cyan]→[/cyan] {tool_name}")
56 if args_str:
57 console.print(f" [dim]{args_str}[/dim]")
58 else:
59 # Detailed format
60 console.print(Panel(
61 f"[bold]{tool_name}[/bold]\n" +
62 "\n".join(f"{k}: {v}" for k, v in args.items()),
63 border_style="cyan",
64 padding=(0, 1)
65 ))
67 @staticmethod
68 def show_tool_result(result: str, success: bool = True, duration: Optional[float] = None):
69 """Display tool result"""
70 # Truncate long results
71 display_result = result[:200] + ("..." if len(result) > 200 else "")
73 if success:
74 status = "[green]✓[/green]"
75 if duration:
76 status += f" [dim]{duration:.2f}s[/dim]"
77 else:
78 status = "[red]✗[/red]"
80 console.print(f" {status} [dim]{display_result}[/dim]")
82 @staticmethod
83 def show_tool_progress(message: str, spinner_type: str = "dots"):
84 """Show progress spinner"""
85 return Spinner(spinner_type, text=message)
88class ThoughtDisplay:
89 """
90 Display model's reasoning process (Gemini-style)
91 """
93 @staticmethod
94 def show_thought(thought: str):
95 """Display a thought bubble"""
96 console.print(f"\n[dim]💭 {thought}[/dim]")
98 @staticmethod
99 def show_planning(steps: list):
100 """Display planning steps"""
101 console.print("\n[bold]Planning:[/bold]")
102 for i, step in enumerate(steps, 1):
103 console.print(f" {i}. [dim]{step}[/dim]")
104 console.print()
107class MemoryDisplay:
108 """
109 Display agent memory (Letta-inspired)
110 """
112 @staticmethod
113 def show_memory_card(agent_name: str, memory: dict):
114 """Display memory as a card"""
115 table = Table(show_header=False, box=None, padding=(0, 1))
116 table.add_column(style="cyan")
117 table.add_column()
119 table.add_row("Agent:", agent_name)
121 if "persona" in memory:
122 persona = memory["persona"][:50] + "..." if len(memory["persona"]) > 50 else memory["persona"]
123 table.add_row("Persona:", persona)
125 if "skills" in memory:
126 skills = ", ".join(memory["skills"][:3])
127 if len(memory["skills"]) > 3:
128 skills += f" +{len(memory['skills']) - 3} more"
129 table.add_row("Skills:", skills)
131 if "facts" in memory:
132 table.add_row("Facts:", f"{len(memory['facts'])} stored")
134 console.print(Panel(
135 table,
136 title="[bold]Agent Memory[/bold]",
137 border_style="blue",
138 padding=(0, 1)
139 ))
141 @staticmethod
142 def show_memory_update(key: str, value: str, operation: str = "add"):
143 """Show memory being updated"""
144 if operation == "add":
145 console.print(f"[dim]💾 Remembered: {key} = {value[:50]}...[/dim]")
146 elif operation == "remove":
147 console.print(f"[dim]🗑️ Forgot: {key}[/dim]")
150class SessionDisplay:
151 """
152 Session summary display (Gemini-inspired)
153 """
155 @staticmethod
156 def show_summary(
157 duration: str,
158 tools_used: int,
159 files_modified: int,
160 key_actions: list
161 ):
162 """Display session summary"""
163 console.print("\n" + "─" * 50)
164 console.print("[bold]Session Summary[/bold]")
165 console.print("─" * 50)
166 console.print(f"Duration: {duration}")
167 console.print(f"Tools used: {tools_used}")
168 console.print(f"Files modified: {files_modified}")
170 if key_actions:
171 console.print("\nKey actions:")
172 for action in key_actions:
173 console.print(f" • {action}")
175 console.print()
178class AgentSelector:
179 """
180 Agent selection interface (Letta-inspired)
181 """
183 @staticmethod
184 def show_selector(agents: list) -> Optional[str]:
185 """Display agent selector and return selected agent"""
186 console.print("\n[bold]Select Agent[/bold]\n")
188 for i, agent in enumerate(agents, 1):
189 name = agent["name"]
190 last_used = agent.get("last_used", "never")
191 console.print(f" {i}. {name} [dim](last used {last_used})[/dim]")
193 console.print(f" {len(agents) + 1}. [cyan]+ Create new agent[/cyan]")
195 try:
196 choice = input("\n> ").strip()
197 if choice.isdigit():
198 idx = int(choice) - 1
199 if 0 <= idx < len(agents):
200 return agents[idx]["name"]
201 elif idx == len(agents):
202 return "__new__"
203 except (ValueError, KeyboardInterrupt):
204 pass
206 return None
209class ProgressTracker:
210 """
211 Track and display progress for long-running operations
212 """
214 def __init__(self, total: int, description: str = "Processing"):
215 self.total = total
216 self.current = 0
217 self.description = description
218 self.start_time = time.time()
220 def update(self, increment: int = 1):
221 """Update progress"""
222 self.current += increment
223 elapsed = time.time() - self.start_time
225 if self.total > 0:
226 percent = (self.current / self.total) * 100
227 console.print(
228 f"\r{self.description}... [{self.current}/{self.total}] "
229 f"{percent:.0f}% ({elapsed:.1f}s)",
230 end=""
231 )
232 else:
233 console.print(
234 f"\r{self.description}... {self.current} items ({elapsed:.1f}s)",
235 end=""
236 )
238 def complete(self):
239 """Mark as complete"""
240 elapsed = time.time() - self.start_time
241 console.print(
242 f"\r{self.description}... [green]✓[/green] "
243 f"({self.current} items, {elapsed:.1f}s)"
244 )
247# Convenience functions
248def stream_text(text: str):
249 """Quick function to stream text"""
250 display = StreamingDisplay()
251 asyncio.run(display.stream_text(text))
254def show_tool(name: str, args: dict, result: str = None, success: bool = True):
255 """Quick function to show tool execution"""
256 ToolDisplay.show_tool_call(name, args)
257 if result:
258 ToolDisplay.show_tool_result(result, success)
261def show_thought(thought: str):
262 """Quick function to show thought"""
263 ThoughtDisplay.show_thought(thought)
266def show_memory(agent_name: str, memory: dict):
267 """Quick function to show memory"""
268 MemoryDisplay.show_memory_card(agent_name, memory)