diff --git a/EvoScientist/cli/interactive.py b/EvoScientist/cli/interactive.py index 1037cd7..d43d9af 100644 --- a/EvoScientist/cli/interactive.py +++ b/EvoScientist/cli/interactive.py @@ -87,8 +87,6 @@ def print_banner( info.append("/", style="#ffe082 bold") info.append(" for commands", style="#ffe082") console.print(info) - console.print(Text(f" {random.choice(WELCOME_SLOGANS)}", style="dim italic")) - console.print() # ============================================================================= @@ -571,6 +569,10 @@ def cmd_interactive( send_thinking=channel_send_thinking, ) + # Slogan — after channels, right before user input + console.print(Text(f" {random.choice(WELCOME_SLOGANS)}", style="dim italic")) + console.print() + try: _print_separator() while state["running"]: diff --git a/EvoScientist/cli/tui_interactive.py b/EvoScientist/cli/tui_interactive.py index 340bfc7..03590aa 100644 --- a/EvoScientist/cli/tui_interactive.py +++ b/EvoScientist/cli/tui_interactive.py @@ -9,6 +9,7 @@ from __future__ import annotations import asyncio import logging import queue +import random import shlex from typing import Any, Callable @@ -40,7 +41,7 @@ from ..sessions import ( from ..stream.events import stream_agent_events from ..stream.state import StreamState, _INTERNAL_TOOLS -from ._constants import LOGO_LINES, LOGO_GRADIENT, build_metadata +from ._constants import LOGO_LINES, LOGO_GRADIENT, WELCOME_SLOGANS, build_metadata _channel_logger = logging.getLogger(__name__) @@ -111,6 +112,8 @@ def _build_welcome_banner( info.append(" for commands", style="#ffe082") banner.append_text(info) + slogan = Text(f"\n {random.choice(WELCOME_SLOGANS)}", style="dim italic") + # Channels panel if channels: from rich.panel import Panel @@ -132,8 +135,10 @@ def _build_welcome_banner( body = Text("\n").join(lines) border = "green" if all_ok else "dim" panel = Panel(body, title="[bold]Channels[/bold]", border_style=border, expand=False) - return Group(banner, Text(""), panel) + return Group(banner, Text(""), panel, slogan) + # No channels — append slogan directly to banner + banner.append_text(slogan) return banner @@ -375,16 +380,22 @@ def run_textual_interactive( on_thinking_cb: Callable[[str], None] | None = None, on_todo_cb: Callable[[list[dict]], None] | None = None, on_media_cb: Callable[[str], None] | None = None, + skip_user_message: bool = False, ) -> str: """Stream agent events and mount widgets. Returns response text. Shared by ``_run_turn`` (interactive) and ``_process_channel_message`` (channel). + + Args: + skip_user_message: If True, don't mount UserMessage (caller + already mounted it — e.g. channel messages with labels). """ container = self.query_one("#chat", VerticalScroll) # 1. Mount user message + loading spinner - await container.mount(UserMessage(user_text)) + if not skip_user_message: + await container.mount(UserMessage(user_text)) loading = LoadingWidget() await container.mount(loading) container.scroll_end(animate=False) @@ -841,17 +852,28 @@ def run_textual_interactive( prompt.focus() async def _process_channel_message(self, msg: ChannelMessage) -> None: - """Process a channel message: stream agent response and reply.""" + """Process a channel message: stream agent response and reply. + + Display order (matches Rich CLI): + > message content + [channel: Received from sender] + (streaming response) + [channel: Replied to sender] + """ self._busy = True self._render_status() prompt_widget = self.query_one("#prompt", Input) prompt_widget.disabled = True + # Mount user message first, then "Received" label + container = self.query_one("#chat", VerticalScroll) + await container.mount(UserMessage(msg.content)) self._append_system( f"[{msg.channel_type}: Received from {msg.sender}]", style="dim", ) + container.scroll_end(animate=False) # Build channel callbacks (fire-and-forget to avoid blocking UI) def _send_to_channel(coro, label: str) -> None: @@ -908,6 +930,7 @@ def run_textual_interactive( on_thinking_cb=_send_thinking if self._channel_send_thinking else None, on_todo_cb=_send_todo, on_media_cb=_send_media, + skip_user_message=True, ) except Exception as exc: response = f"Error: {exc}"