feat(tui): enhance spinner animations and update renderer styles
This commit is contained in:
@@ -13,12 +13,14 @@ class ViewAgentGraphRenderer(BaseToolRenderer):
|
|||||||
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
css_classes: ClassVar[list[str]] = ["tool-call", "agents-graph-tool"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static: # noqa: ARG003
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
||||||
text = Text()
|
status = tool_data.get("status", "unknown")
|
||||||
text.append("🕸️ ")
|
|
||||||
text.append("Viewing agents graph", style="bold #fbbf24")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
text = Text()
|
||||||
|
text.append("◇ ", style="#a78bfa")
|
||||||
|
text.append("viewing agents graph", style="dim")
|
||||||
|
|
||||||
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -30,22 +32,21 @@ class CreateAgentRenderer(BaseToolRenderer):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
|
status = tool_data.get("status", "unknown")
|
||||||
|
|
||||||
task = args.get("task", "")
|
task = args.get("task", "")
|
||||||
name = args.get("name", "Agent")
|
name = args.get("name", "Agent")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("🤖 ")
|
text.append("◈ ", style="#a78bfa")
|
||||||
text.append(f"Creating {name}", style="bold #fbbf24")
|
text.append("spawning ", style="dim")
|
||||||
|
text.append(name, style="bold #a78bfa")
|
||||||
|
|
||||||
if task:
|
if task:
|
||||||
text.append("\n ")
|
text.append("\n ")
|
||||||
text.append(task, style="dim")
|
text.append(task, style="dim")
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Spawning agent...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,21 +58,23 @@ class SendMessageToAgentRenderer(BaseToolRenderer):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
|
status = tool_data.get("status", "unknown")
|
||||||
|
|
||||||
message = args.get("message", "")
|
message = args.get("message", "")
|
||||||
|
agent_id = args.get("agent_id", "")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("💬 ")
|
text.append("→ ", style="#60a5fa")
|
||||||
text.append("Sending message", style="bold #fbbf24")
|
if agent_id:
|
||||||
|
text.append(f"to {agent_id}", style="dim")
|
||||||
|
else:
|
||||||
|
text.append("sending message", style="dim")
|
||||||
|
|
||||||
if message:
|
if message:
|
||||||
text.append("\n ")
|
text.append("\n ")
|
||||||
text.append(message, style="dim")
|
text.append(message, style="dim")
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Sending...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|
||||||
|
|
||||||
@@ -120,19 +123,17 @@ class WaitForMessageRenderer(BaseToolRenderer):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def render(cls, tool_data: dict[str, Any]) -> Static:
|
def render(cls, tool_data: dict[str, Any]) -> Static:
|
||||||
args = tool_data.get("args", {})
|
args = tool_data.get("args", {})
|
||||||
|
status = tool_data.get("status", "unknown")
|
||||||
|
|
||||||
reason = args.get("reason", "Waiting for messages from other agents or user input")
|
reason = args.get("reason", "")
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("⏸️ ")
|
text.append("○ ", style="#6b7280")
|
||||||
text.append("Waiting for messages", style="bold #fbbf24")
|
text.append("waiting", style="dim")
|
||||||
|
|
||||||
if reason:
|
if reason:
|
||||||
text.append("\n ")
|
text.append("\n ")
|
||||||
text.append(reason, style="dim")
|
text.append(reason, style="dim")
|
||||||
else:
|
|
||||||
text.append("\n ")
|
|
||||||
text.append("Agent paused until message received...", style="dim")
|
|
||||||
|
|
||||||
css_classes = cls.get_css_classes("completed")
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|||||||
@@ -55,12 +55,13 @@ class SubagentStartInfoRenderer(BaseToolRenderer):
|
|||||||
task = str(args.get("task", ""))
|
task = str(args.get("task", ""))
|
||||||
|
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("🤖 Spawned subagent ")
|
text.append("◈ ", style="#a78bfa")
|
||||||
text.append(name)
|
text.append("subagent ", style="dim")
|
||||||
|
text.append(name, style="bold #a78bfa")
|
||||||
|
|
||||||
if task:
|
if task:
|
||||||
text.append("\n Task: ")
|
text.append("\n ")
|
||||||
text.append(task)
|
text.append(task, style="dim")
|
||||||
|
|
||||||
css_classes = cls.get_css_classes(status)
|
css_classes = cls.get_css_classes(status)
|
||||||
return Static(text, classes=css_classes)
|
return Static(text, classes=css_classes)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
|
|||||||
from rich.align import Align
|
from rich.align import Align
|
||||||
from rich.console import Group
|
from rich.console import Group
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
|
from rich.spinner import SPINNERS
|
||||||
from rich.style import Style
|
from rich.style import Style
|
||||||
from rich.text import Text
|
from rich.text import Text
|
||||||
from textual import events, on
|
from textual import events, on
|
||||||
@@ -346,7 +347,8 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
]
|
]
|
||||||
self._agent_verbs: dict[str, str] = {} # agent_id -> current_verb
|
self._agent_verbs: dict[str, str] = {} # agent_id -> current_verb
|
||||||
self._agent_verb_timers: dict[str, Any] = {} # agent_id -> timer
|
self._agent_verb_timers: dict[str, Any] = {} # agent_id -> timer
|
||||||
self._agent_dot_states: dict[str, float] = {} # agent_id -> shine position
|
self._spinner_frame_index: int = 0 # Current spinner frame index
|
||||||
|
self._spinner_frames: list[str] = list(SPINNERS["dots"]["frames"]) # Braille spinner frames
|
||||||
self._dot_animation_timer: Any | None = None
|
self._dot_animation_timer: Any | None = None
|
||||||
|
|
||||||
self._last_streaming_content: dict[str, str] = {}
|
self._last_streaming_content: dict[str, str] = {}
|
||||||
@@ -895,30 +897,20 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
if self.selected_agent_id == agent_id:
|
if self.selected_agent_id == agent_id:
|
||||||
self._update_agent_status_display()
|
self._update_agent_status_display()
|
||||||
|
|
||||||
def _get_shine_style(self, dist: float) -> Style:
|
def _get_animated_verb_text(self, agent_id: str, verb: str) -> Text: # noqa: ARG002
|
||||||
if dist <= 0.5:
|
|
||||||
return Style(color="bright_white", bold=True)
|
|
||||||
if dist <= 1.5:
|
|
||||||
return Style(color="white", bold=True)
|
|
||||||
if dist <= 2.5:
|
|
||||||
return Style(color="#a3a3a3")
|
|
||||||
return Style(color="#525252")
|
|
||||||
|
|
||||||
def _get_animated_verb_text(self, agent_id: str, verb: str) -> Text:
|
|
||||||
if agent_id not in self._agent_dot_states:
|
|
||||||
self._agent_dot_states[agent_id] = 0.0
|
|
||||||
|
|
||||||
shine_pos = self._agent_dot_states[agent_id]
|
|
||||||
text = Text()
|
text = Text()
|
||||||
for i, char in enumerate(verb):
|
spinner_char = self._spinner_frames[self._spinner_frame_index % len(self._spinner_frames)]
|
||||||
dist = abs(i - shine_pos)
|
text.append(spinner_char, style=Style(color="#22c55e"))
|
||||||
text.append(char, style=self._get_shine_style(dist))
|
text.append(" ", style=Style(color="white"))
|
||||||
|
text.append(verb, style=Style(color="white"))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def _get_animated_waiting_text(self, agent_id: str) -> Text: # noqa: ARG002
|
def _get_animated_waiting_text(self, agent_id: str) -> Text: # noqa: ARG002
|
||||||
text = Text()
|
text = Text()
|
||||||
text.append("Waiting", style="#fbbf24")
|
spinner_char = self._spinner_frames[self._spinner_frame_index % len(self._spinner_frames)]
|
||||||
|
text.append(spinner_char, style=Style(color="#fbbf24"))
|
||||||
|
text.append(" ", style=Style(color="white"))
|
||||||
|
text.append("Waiting", style=Style(color="#fbbf24"))
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def _start_dot_animation(self) -> None:
|
def _start_dot_animation(self) -> None:
|
||||||
@@ -938,16 +930,8 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
status = agent_data.get("status", "running")
|
status = agent_data.get("status", "running")
|
||||||
if status in ["running", "waiting"]:
|
if status in ["running", "waiting"]:
|
||||||
has_active_agents = True
|
has_active_agents = True
|
||||||
if status == "waiting":
|
self._spinner_frame_index = (self._spinner_frame_index + 1) % len(
|
||||||
verb = "Waiting"
|
self._spinner_frames
|
||||||
elif self._agent_has_real_activity(self.selected_agent_id):
|
|
||||||
verb = self._get_agent_verb(self.selected_agent_id)
|
|
||||||
else:
|
|
||||||
verb = "Initializing Agent"
|
|
||||||
text_len = len(verb)
|
|
||||||
current_shine = self._agent_dot_states.get(self.selected_agent_id, 0.0)
|
|
||||||
self._agent_dot_states[self.selected_agent_id] = (current_shine + 0.5) % (
|
|
||||||
text_len + 3
|
|
||||||
)
|
)
|
||||||
self._update_agent_status_display()
|
self._update_agent_status_display()
|
||||||
|
|
||||||
@@ -959,11 +943,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
if not has_active_agents:
|
if not has_active_agents:
|
||||||
self._stop_dot_animation()
|
self._stop_dot_animation()
|
||||||
for agent_id in list(self._agent_dot_states.keys()):
|
self._spinner_frame_index = 0
|
||||||
if agent_id not in self.tracer.agents or self.tracer.agents[agent_id].get(
|
|
||||||
"status"
|
|
||||||
) not in ["running", "waiting"]:
|
|
||||||
del self._agent_dot_states[agent_id]
|
|
||||||
|
|
||||||
def _agent_has_real_activity(self, agent_id: str) -> bool:
|
def _agent_has_real_activity(self, agent_id: str) -> bool:
|
||||||
initial_tools = {"scan_start_info", "subagent_start_info"}
|
initial_tools = {"scan_start_info", "subagent_start_info"}
|
||||||
|
|||||||
Reference in New Issue
Block a user