Skip to content

Conversation

@Aaronontheweb
Copy link
Owner

@Aaronontheweb Aaronontheweb commented Dec 12, 2025

Summary

Add streaming text infrastructure for terminal UIs with two modes of operation:

  • Persisted mode (StreamMode.Persisted): Full buffer retained with scroll support, auto-scroll when at bottom, viewport-based rendering. Suitable for chat messages, logs, full LLM output.

  • Windowed mode (StreamMode.Windowed): Rolling buffer (last N lines), no scroll, "ticker tape" effect. Suitable for ephemeral content like thinking indicators where only recent content matters.

Components Added

Component Purpose
StreamMode Enum defining retention policies
IStreamingTextBuffer Interface for buffer abstraction
PersistedStreamBuffer Full history with scroll state management
WindowedStreamBuffer Rolling window with discard tracking
WordWrapper Utility for terminal width handling
StreamingText Spectre.Console Component that wraps buffers

Key Features

  • Thread-safe buffer operations with locking
  • Word wrapping with viewport width awareness
  • Scroll state tracking (user-scrolled detection, auto-scroll toggle)
  • Cached wrapped line counts with dirty flags for performance
  • Prefix support with separate styling for chat-style UIs

Usage Examples

```csharp
// Persisted mode for chat/logs (default)
var chat = StreamingText.CreatePersisted();
chat.Append("Hello ");
chat.Append("world!\n");
chat.ScrollUp(3);

// Windowed mode for thinking indicators
var thinking = StreamingText.CreateWindowed(windowSize: 2);
thinking.Append("Processing step 1...\n");
thinking.Append("Processing step 2...\n");
thinking.Append("Processing step 3...\n"); // Step 1 is now discarded
```

Demo Application

Added a streaming chat demo using Akka.NET to simulate LLM behavior:

  • LlmSimulatorActor: Actor that generates thinking tokens followed by streaming text chunks
  • StreamingChatPage: Demo page with floating panel layout
  • StreamingChatViewModel: Handles text input, actor messages, and UI state

The demo showcases both streaming modes:

  • Chat history uses persisted mode (scrollable full output)
  • Thinking indicator uses windowed mode (last 3 lines only)

Test Plan

  • 89 new unit tests covering:
    • Buffer append/clear operations
    • Word wrapping edge cases (long words, whitespace, exact width)
    • Scroll behavior (up/down/top/bottom)
    • Auto-scroll toggle on user interaction
    • Windowed mode discard tracking
    • Component integration with Spectre.Console
  • All 163 tests pass
  • Demo builds and links with Akka.Hosting 1.5.55.1

Implement streaming text infrastructure for terminal UIs with two modes:

- **Persisted mode**: Full buffer retained with scroll support, auto-scroll
  when at bottom, viewport-based rendering. Suitable for chat, logs, LLM output.

- **Windowed mode**: Rolling buffer (last N lines), no scroll, "ticker tape"
  effect. Suitable for ephemeral content like thinking indicators.

Components:
- StreamMode enum defining retention policies
- IStreamingTextBuffer interface for buffer abstraction
- PersistedStreamBuffer with scroll state management
- WindowedStreamBuffer with discard tracking
- WordWrapper utility for terminal width handling
- StreamingText component using Spectre.Console primitives

Includes 89 unit tests covering buffer operations, scroll behavior,
word wrapping edge cases, and component integration.
Demo application showcasing:
- StreamingText components (persisted chat history + windowed thinking)
- Akka.NET actor-based LLM simulation with thinking tokens
- Text input for prompts with cursor navigation
- Floating panel layout with status bar

LlmSimulatorActor generates:
- Thinking tokens (displayed in windowed mode)
- Streaming text chunks (displayed in persisted mode)
- Simulated delays for realistic streaming effect
- Add ContentChanged observable and MarkDirty() to Component base class
  for async UI update marshalling (similar to Control.Invoke in WinForms)
- Move streaming chat demo to Termina.Demo.Streaming with Akka Streams
- Update StreamingText to use base class MarkDirty() pattern
- Add RequestRedraw action to ReactiveViewModel for components to signal
  UI updates needed from async operations
- Add RedrawRequested event type for the application event channel
- Add VirtualInputSource.EnqueueKey overload with modifiers
- Change quit key from Q to Ctrl+Q to avoid interfering with text input
- Add IAsyncEnumerable consumption methods to StreamingText
- Add auto-scroll tests for PersistedStreamBuffer
- Update TextInput with cursor movement and editing improvements
- Remove viewport/scrolling logic from StreamingText (now renders all content)
- Add MaxWidth property for word wrapping (replaces ViewportWidth)
- Remove ScrollUp/ScrollDown/ScrollToBottom methods (no longer needed)
- Add StreamingText.Create() factory method (replaces CreatePersisted/CreateUnbounded)
- Keep CreateWindowed() for rolling window mode (thinking indicators)

- Add blinking cursor support to TextInput component
- Add CursorBlink and BlinkIntervalMs properties
- Implement IDisposable for timer cleanup
- Reset cursor blink on keypress for better UX

- Update StreamingChatViewModel to use simplified API
- Remove fixed Height from chat history Panel (allows content growth)
- Add logging level config to suppress info logs in demo

- Add RenderMode enum (LiveDisplay vs Scrolling) for future use

Design principle: StreamingText handles buffering/rendering, terminal
scrollback handles scrolling. For fixed-height scrolling, a separate
ScrollableViewport component can wrap StreamingText (composition).
@Aaronontheweb Aaronontheweb enabled auto-merge (squash) December 12, 2025 14:34
@Aaronontheweb Aaronontheweb merged commit 9cac515 into dev Dec 12, 2025
6 checks passed
@Aaronontheweb Aaronontheweb deleted the feature/streaming-text-components branch December 12, 2025 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant