yippie
This commit is contained in:
150
core/tui.py
Normal file
150
core/tui.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
# --- ANSI Styling Palette ---
|
||||
CYAN = "\033[96m"
|
||||
GREEN = "\033[92m"
|
||||
YELLOW = "\033[93m"
|
||||
RED = "\033[91m"
|
||||
RESET = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
DIM = "\033[2m"
|
||||
CLEAR = "\033[2J\033[H"
|
||||
|
||||
# --- ANSI Layout Sequences ---
|
||||
SAVE_CURSOR = "\033[s"
|
||||
RESTORE_CURSOR = "\033[u"
|
||||
CLEAR_LINE = "\033[K"
|
||||
|
||||
class TerminalUIManager:
|
||||
def __init__(self):
|
||||
self.term_width, self.term_height = os.get_terminal_size()
|
||||
self.current_persona = "Orchestrator"
|
||||
self.active_tool = None
|
||||
self.token_usage = 0
|
||||
self.max_tokens = 16384
|
||||
self.is_thinking = False
|
||||
self.has_set_region = False
|
||||
|
||||
def setup_screen(self):
|
||||
"""Initializes the terminal layout with a scrolling region."""
|
||||
sys.stdout.write(CLEAR)
|
||||
sys.stdout.flush()
|
||||
# Set scrolling region from row 2 to (height - 1)
|
||||
self.term_width, self.term_height = os.get_terminal_size()
|
||||
sys.stdout.write(f"\033[2;{self.term_height - 1}r")
|
||||
# Move cursor to row 2
|
||||
sys.stdout.write("\033[2;1H")
|
||||
sys.stdout.flush()
|
||||
self.has_set_region = True
|
||||
self.redraw_header()
|
||||
self.redraw_footer()
|
||||
|
||||
def cleanup(self):
|
||||
"""Restores the terminal scrolling region."""
|
||||
if self.has_set_region:
|
||||
sys.stdout.write("\033[r") # Reset scrolling region
|
||||
sys.stdout.write(f"\033[{self.term_height};1H")
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def update_state(self, persona=None, active_tool=None, token_usage=None):
|
||||
"""Update internal state and redraw header/footer if necessary."""
|
||||
changed_header = False
|
||||
changed_footer = False
|
||||
|
||||
if persona is not None and persona != self.current_persona:
|
||||
self.current_persona = persona
|
||||
changed_header = True
|
||||
|
||||
if active_tool is not None and active_tool != self.active_tool:
|
||||
self.active_tool = active_tool
|
||||
changed_footer = True
|
||||
|
||||
if token_usage is not None and token_usage != self.token_usage:
|
||||
self.token_usage = token_usage
|
||||
changed_footer = True
|
||||
|
||||
if changed_header:
|
||||
self.redraw_header()
|
||||
if changed_footer:
|
||||
self.redraw_footer()
|
||||
|
||||
def redraw_header(self):
|
||||
"""Draws the fixed top header."""
|
||||
sys.stdout.write(SAVE_CURSOR)
|
||||
sys.stdout.write("\033[1;1H") # Move to top left
|
||||
|
||||
persona_name = self.current_persona.split('/')[-1].capitalize()
|
||||
header_text = f"⚙ SmarterAgents │ Persona: {persona_name}"
|
||||
padding = max(0, self.term_width - len(header_text) - 1)
|
||||
|
||||
sys.stdout.write(f"{CYAN}{BOLD}{header_text} {'─' * padding}{RESET}{CLEAR_LINE}")
|
||||
sys.stdout.write(RESTORE_CURSOR)
|
||||
sys.stdout.flush()
|
||||
|
||||
def redraw_footer(self):
|
||||
"""Draws the fixed bottom footer."""
|
||||
sys.stdout.write(SAVE_CURSOR)
|
||||
sys.stdout.write(f"\033[{self.term_height};1H") # Move to bottom left
|
||||
|
||||
# Calculate budget bar
|
||||
pct = min(1.0, self.token_usage / self.max_tokens) if self.max_tokens > 0 else 0
|
||||
filled = int(pct * 10)
|
||||
empty = 10 - filled
|
||||
bar = "█" * filled + "░" * empty
|
||||
pct_str = f"{int(pct * 100)}%"
|
||||
|
||||
status_text = "Idle"
|
||||
if self.active_tool:
|
||||
status_text = f"⚡ Running Tool: {self.active_tool}"
|
||||
|
||||
footer_text = f" Token Budget: [{bar}] {pct_str} │ Status: {status_text} "
|
||||
padding = max(0, self.term_width - len(footer_text))
|
||||
|
||||
sys.stdout.write(f"{DIM}{footer_text}{' ' * padding}{RESET}{CLEAR_LINE}")
|
||||
sys.stdout.write(RESTORE_CURSOR)
|
||||
sys.stdout.flush()
|
||||
|
||||
def start_thought(self):
|
||||
"""Starts rendering a thought block."""
|
||||
if not self.is_thinking:
|
||||
self.is_thinking = True
|
||||
sys.stdout.write(f"\n{CYAN}{DIM}[💭 Agent Thinking...]\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def end_thought(self):
|
||||
"""Ends rendering a thought block."""
|
||||
if self.is_thinking:
|
||||
self.is_thinking = False
|
||||
sys.stdout.write(f"{RESET}\n{CYAN}{DIM}[✓ Thought Complete]{RESET}\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def stream_thought_chunk(self, chunk):
|
||||
"""Streams raw thought characters."""
|
||||
sys.stdout.write(chunk)
|
||||
sys.stdout.flush()
|
||||
|
||||
def print_message(self, message, role="Assistant"):
|
||||
"""Prints a standard message."""
|
||||
color = GREEN if role == "Assistant" else YELLOW
|
||||
sys.stdout.write(f"\n{color}{BOLD}{role} >{RESET} {message}\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def log_tool_call(self, tool_name, args):
|
||||
"""Prints a nicely formatted tool invocation box."""
|
||||
sys.stdout.write(f"\n{YELLOW}{BOLD}╭─[🔧 Tool Execution: {tool_name}]" + "─" * max(0, self.term_width - 25 - len(tool_name)) + f"{RESET}\n")
|
||||
try:
|
||||
formatted_args = json.dumps(args, indent=2)
|
||||
for line in formatted_args.splitlines():
|
||||
sys.stdout.write(f"{YELLOW}│{RESET} {line}\n")
|
||||
except:
|
||||
sys.stdout.write(f"{YELLOW}│{RESET} {args}\n")
|
||||
sys.stdout.write(f"{YELLOW}╰" + "─" * (self.term_width - 1) + f"{RESET}\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def print_system_info(self, message, is_error=False):
|
||||
color = RED if is_error else CYAN
|
||||
sys.stdout.write(f"\n{color}{BOLD}[System]{RESET} {message}\n")
|
||||
sys.stdout.flush()
|
||||
Reference in New Issue
Block a user