feat(tui): enhance streaming content handling and animation efficiency
This commit is contained in:
@@ -349,6 +349,9 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
self._agent_dot_states: dict[str, float] = {} # agent_id -> shine position
|
self._agent_dot_states: dict[str, float] = {} # agent_id -> shine position
|
||||||
self._dot_animation_timer: Any | None = None
|
self._dot_animation_timer: Any | None = None
|
||||||
|
|
||||||
|
self._last_streaming_content: dict[str, str] = {}
|
||||||
|
self._streaming_update_counter: int = 0
|
||||||
|
|
||||||
self._setup_cleanup_handlers()
|
self._setup_cleanup_handlers()
|
||||||
|
|
||||||
def _build_scan_config(self, args: argparse.Namespace) -> dict[str, Any]:
|
def _build_scan_config(self, args: argparse.Namespace) -> dict[str, Any]:
|
||||||
@@ -491,7 +494,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
self._start_scan_thread()
|
self._start_scan_thread()
|
||||||
|
|
||||||
self.set_interval(0.1, self._update_ui_from_tracer)
|
self.set_interval(0.25, self._update_ui_from_tracer)
|
||||||
|
|
||||||
def _update_ui_from_tracer(self) -> None:
|
def _update_ui_from_tracer(self) -> None:
|
||||||
if self.show_splash:
|
if self.show_splash:
|
||||||
@@ -570,11 +573,51 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _update_chat_view(self) -> None:
|
def _should_skip_streaming_update(self, streaming: str, current_event_ids: list[str]) -> bool:
|
||||||
if len(self.screen_stack) > 1 or self.show_splash:
|
streaming_hash = str(len(streaming))
|
||||||
return
|
last_hash = self._last_streaming_content.get(self.selected_agent_id or "", "")
|
||||||
|
self._streaming_update_counter += 1
|
||||||
|
|
||||||
if not self.is_mounted:
|
if (
|
||||||
|
streaming_hash == last_hash
|
||||||
|
and self._streaming_update_counter % 4 != 0
|
||||||
|
and current_event_ids == self._displayed_events
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if self.selected_agent_id:
|
||||||
|
self._last_streaming_content[self.selected_agent_id] = streaming_hash
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_chat_content(
|
||||||
|
self,
|
||||||
|
) -> tuple[Text | None, str | None]:
|
||||||
|
if not self.selected_agent_id:
|
||||||
|
return self._get_chat_placeholder_content(
|
||||||
|
"Select an agent from the tree to see its activity.", "placeholder-no-agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
events = self._gather_agent_events(self.selected_agent_id)
|
||||||
|
streaming = self.tracer.get_streaming_content(self.selected_agent_id)
|
||||||
|
|
||||||
|
if not events and not streaming:
|
||||||
|
return self._get_chat_placeholder_content(
|
||||||
|
"Starting agent...", "placeholder-no-activity"
|
||||||
|
)
|
||||||
|
|
||||||
|
current_event_ids = [e["id"] for e in events]
|
||||||
|
|
||||||
|
if streaming:
|
||||||
|
if self._should_skip_streaming_update(streaming, current_event_ids):
|
||||||
|
return None, None
|
||||||
|
elif current_event_ids == self._displayed_events:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
self._displayed_events = current_event_ids
|
||||||
|
return self._get_rendered_events_content(events), "chat-content"
|
||||||
|
|
||||||
|
def _update_chat_view(self) -> None:
|
||||||
|
if len(self.screen_stack) > 1 or self.show_splash or not self.is_mounted:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -590,24 +633,9 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
except (AttributeError, ValueError):
|
except (AttributeError, ValueError):
|
||||||
is_at_bottom = True
|
is_at_bottom = True
|
||||||
|
|
||||||
if not self.selected_agent_id:
|
content, css_class = self._get_chat_content()
|
||||||
content, css_class = self._get_chat_placeholder_content(
|
if content is None:
|
||||||
"Select an agent from the tree to see its activity.", "placeholder-no-agent"
|
return
|
||||||
)
|
|
||||||
else:
|
|
||||||
events = self._gather_agent_events(self.selected_agent_id)
|
|
||||||
streaming = self.tracer.get_streaming_content(self.selected_agent_id)
|
|
||||||
if not events and not streaming:
|
|
||||||
content, css_class = self._get_chat_placeholder_content(
|
|
||||||
"Starting agent...", "placeholder-no-activity"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
current_event_ids = [e["id"] for e in events]
|
|
||||||
if current_event_ids == self._displayed_events and not streaming:
|
|
||||||
return
|
|
||||||
content = self._get_rendered_events_content(events)
|
|
||||||
css_class = "chat-content"
|
|
||||||
self._displayed_events = current_event_ids
|
|
||||||
|
|
||||||
chat_display = self.query_one("#chat_display", Static)
|
chat_display = self.query_one("#chat_display", Static)
|
||||||
self._safe_widget_operation(chat_display.update, content)
|
self._safe_widget_operation(chat_display.update, content)
|
||||||
@@ -895,7 +923,7 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
|
|
||||||
def _start_dot_animation(self) -> None:
|
def _start_dot_animation(self) -> None:
|
||||||
if self._dot_animation_timer is None:
|
if self._dot_animation_timer is None:
|
||||||
self._dot_animation_timer = self.set_interval(0.008, self._animate_dots)
|
self._dot_animation_timer = self.set_interval(0.05, self._animate_dots)
|
||||||
|
|
||||||
def _stop_dot_animation(self) -> None:
|
def _stop_dot_animation(self) -> None:
|
||||||
if self._dot_animation_timer is not None:
|
if self._dot_animation_timer is not None:
|
||||||
@@ -905,29 +933,30 @@ class StrixTUIApp(App): # type: ignore[misc]
|
|||||||
def _animate_dots(self) -> None:
|
def _animate_dots(self) -> None:
|
||||||
has_active_agents = False
|
has_active_agents = False
|
||||||
|
|
||||||
for agent_id, agent_data in list(self.tracer.agents.items()):
|
if self.selected_agent_id and self.selected_agent_id in self.tracer.agents:
|
||||||
|
agent_data = self.tracer.agents[self.selected_agent_id]
|
||||||
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":
|
if status == "waiting":
|
||||||
verb = "Waiting"
|
verb = "Waiting"
|
||||||
elif self._agent_has_real_activity(agent_id):
|
elif self._agent_has_real_activity(self.selected_agent_id):
|
||||||
verb = self._get_agent_verb(agent_id)
|
verb = self._get_agent_verb(self.selected_agent_id)
|
||||||
else:
|
else:
|
||||||
verb = "Initializing Agent"
|
verb = "Initializing Agent"
|
||||||
text_len = len(verb)
|
text_len = len(verb)
|
||||||
current_shine = self._agent_dot_states.get(agent_id, 0.0)
|
current_shine = self._agent_dot_states.get(self.selected_agent_id, 0.0)
|
||||||
self._agent_dot_states[agent_id] = (current_shine + 0.12) % (text_len + 3)
|
self._agent_dot_states[self.selected_agent_id] = (current_shine + 0.5) % (
|
||||||
|
text_len + 3
|
||||||
if (
|
)
|
||||||
has_active_agents
|
|
||||||
and self.selected_agent_id
|
|
||||||
and self.selected_agent_id in self.tracer.agents
|
|
||||||
):
|
|
||||||
selected_status = self.tracer.agents[self.selected_agent_id].get("status", "running")
|
|
||||||
if selected_status in ["running", "waiting"]:
|
|
||||||
self._update_agent_status_display()
|
self._update_agent_status_display()
|
||||||
|
|
||||||
|
if not has_active_agents:
|
||||||
|
has_active_agents = any(
|
||||||
|
agent_data.get("status", "running") in ["running", "waiting"]
|
||||||
|
for agent_data in self.tracer.agents.values()
|
||||||
|
)
|
||||||
|
|
||||||
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()):
|
for agent_id in list(self._agent_dot_states.keys()):
|
||||||
|
|||||||
Reference in New Issue
Block a user