Skip to content
loading logo

Loading

loading

Imported skill loading from langchain

SKILL.md

Full skill instructions

"""Loading widget with animated spinner for agent activity."""

from future import annotations

from time import time from typing import TYPE_CHECKING, ClassVar

from textual.containers import Horizontal from textual.widgets import Static

if TYPE_CHECKING: from textual.app import ComposeResult

class BrailleSpinner: """Animated braille spinner."""

FRAMES: ClassVar[tuple[str, ...]] = (
    "⠋",
    "⠙",
    "⠹",
    "⠸",
    "⠼",
    "⠴",
    "⠦",
    "⠧",
    "⠇",
    "⠏",
)

def __init__(self) -> None:
    """Initialize spinner."""
    self._position = 0

def next_frame(self) -> str:
    """Get next animation frame."""
    frame = self.FRAMES[self._position]
    self._position = (self._position + 1) % len(self.FRAMES)
    return frame

def current_frame(self) -> str:
    """Get current frame without advancing."""
    return self.FRAMES[self._position]

class LoadingWidget(Static): """Animated loading indicator with status text and elapsed time.

Displays: ⠋ Thinking... (3s, esc to interrupt)
"""

DEFAULT_CSS = """
LoadingWidget {
    height: auto;
    padding: 0 1;
}

LoadingWidget .loading-container {
    height: auto;
    width: 100%;
}

LoadingWidget .loading-spinner {
    width: auto;
    color: $warning;
}

LoadingWidget .loading-status {
    width: auto;
    color: $warning;
}

LoadingWidget .loading-hint {
    width: auto;
    color: $text-muted;
    margin-left: 1;
}
"""

def __init__(self, status: str = "Thinking") -> None:
    """Initialize loading widget.

    Args:
        status: Initial status text to display
    """
    super().__init__()
    self._status = status
    self._spinner = BrailleSpinner()
    self._start_time: float | None = None
    self._spinner_widget: Static | None = None
    self._status_widget: Static | None = None
    self._hint_widget: Static | None = None
    self._paused = False
    self._paused_elapsed: int = 0

def compose(self) -> ComposeResult:
    """Compose the loading widget layout."""
    with Horizontal(classes="loading-container"):
        self._spinner_widget = Static(self._spinner.current_frame(), classes="loading-spinner")
        yield self._spinner_widget

        self._status_widget = Static(f" {self._status}... ", classes="loading-status")
        yield self._status_widget

        self._hint_widget = Static("(0s, esc to interrupt)", classes="loading-hint")
        yield self._hint_widget

def on_mount(self) -> None:
    """Start animation on mount."""
    self._start_time = time()
    self.set_interval(0.1, self._update_animation)

def _update_animation(self) -> None:
    """Update spinner and elapsed time."""
    if self._paused:
        return

    if self._spinner_widget:
        frame = self._spinner.next_frame()
        self._spinner_widget.update(f"[#FFD800]{frame}[/]")

    if self._hint_widget and self._start_time is not None:
        elapsed = int(time() - self._start_time)
        self._hint_widget.update(f"({elapsed}s, esc to interrupt)")

def set_status(self, status: str) -> None:
    """Update the status text.

    Args:
        status: New status text
    """
    self._status = status
    if self._status_widget:
        self._status_widget.update(f" {self._status}... ")

def pause(self, status: str = "Awaiting decision") -> None:
    """Pause the animation and update status.

    Args:
        status: Status to show while paused
    """
    self._paused = True
    if self._start_time is not None:
        self._paused_elapsed = int(time() - self._start_time)
    self._status = status
    if self._status_widget:
        self._status_widget.update(f" {status}... ")
    if self._hint_widget:
        self._hint_widget.update(f"(paused at {self._paused_elapsed}s)")
    if self._spinner_widget:
        self._spinner_widget.update("[dim]⏸[/​dim]")

def resume(self) -> None:
    """Resume the animation."""
    self._paused = False
    self._status = "Thinking"
    if self._status_widget:
        self._status_widget.update(f" {self._status}... ")

def stop(self) -> None:
    """Stop the animation (widget will be removed by caller)."""