Adaptive UI

View Source

The adaptive subsystem watches how you interact with the interface -- which panes you focus on, what commands you run, how long you dwell -- and suggests layout changes based on patterns. Accept or reject its suggestions, and it gets better over time.

Architecture

BehaviorTracker (records interactions)
    |
    v
LayoutRecommender (produces layout change suggestions)
    |
    v
FeedbackLoop (tracks accept/reject, optional Nx retraining)

Three GenServers under Raxol.Adaptive.Supervisor (:one_for_one). BehaviorTracker feeds windowed aggregates to LayoutRecommender, which emits suggestions. FeedbackLoop records whether you accepted or rejected them, and can retrain an Nx model if available.

Quick Start

{:ok, _} = Raxol.Adaptive.Supervisor.start_link()

# Record what the user does
Raxol.Adaptive.BehaviorTracker.record(:pane_focus, %{pane: :logs})
Raxol.Adaptive.BehaviorTracker.record(:command_issued, %{command: :deploy})

# Get notified when the system has a suggestion
Raxol.Adaptive.LayoutRecommender.subscribe()
# Receive: {:layout_recommendation, %{action: :expand, target: :logs, ...}}

# Tell it whether the suggestion was good
Raxol.Adaptive.FeedbackLoop.accept(recommendation_id)
Raxol.Adaptive.FeedbackLoop.reject(recommendation_id)

Behavior Tracking

Raxol.Adaptive.BehaviorTracker logs timestamped events and computes windowed aggregates -- pane dwell times, command frequency, that sort of thing.

BehaviorTracker.record(:pane_focus, %{pane: :metrics})
BehaviorTracker.record(:pane_dwell, %{pane: :logs, duration_ms: 5000})
BehaviorTracker.record(:command_issued, %{command: :restart})
BehaviorTracker.record(:scroll_pattern, %{pane: :logs, direction: :down})

aggregates = BehaviorTracker.get_aggregates(5)  # last 5 windows
events = BehaviorTracker.get_recent_events(20)

BehaviorTracker.enable()
BehaviorTracker.disable()

# Real-time aggregate stream
BehaviorTracker.subscribe()
# Receive: {:behavior_aggregate, aggregate}

Event types: :pane_focus, :pane_dwell, :command_issued, :alert_response, :scroll_pattern, :takeover_start, :takeover_end, :layout_override.

Layout Recommendations

Raxol.Adaptive.LayoutRecommender looks at behavior aggregates and suggests layout changes. Uses rule-based logic by default, with optional Nx model support for learned recommendations.

rec = LayoutRecommender.get_last_recommendation()
# => %{action: :expand, target: :logs, confidence: 0.85, ...}

LayoutRecommender.subscribe()
# Receive: {:layout_recommendation, recommendation}

# Nx model support (optional)
LayoutRecommender.set_pane_ids([:metrics, :logs, :alerts])
LayoutRecommender.set_model_params(trained_params)

Actions it can recommend: :hide, :show, :expand, :shrink.

Feedback Loop

Raxol.Adaptive.FeedbackLoop keeps a sliding window of accept/reject decisions so you can track how well the recommendations are doing.

FeedbackLoop.submit_recommendation(recommendation)

FeedbackLoop.accept(recommendation_id)
FeedbackLoop.reject(recommendation_id)

accuracy = FeedbackLoop.get_accuracy()  # 0.0 - 1.0
history = FeedbackLoop.get_history(20)

# Retrain if Nx is available
{:ok, :trained, params} = FeedbackLoop.force_retrain()
# Without Nx:
{:ok, :rule_based_mode} = FeedbackLoop.force_retrain()

Layout Transitions

Raxol.Adaptive.LayoutTransition animates between layouts with easing. Pure functions, no GenServer -- call tick/2 each frame.

alias Raxol.Adaptive.LayoutTransition

transition = LayoutTransition.start(
  %{logs: %{height: 10}, metrics: %{height: 20}},   # from
  %{logs: %{height: 20}, metrics: %{height: 10}},   # to
  duration_ms: 300,
  easing: :ease_in_out
)

# Each frame:
case LayoutTransition.tick(transition, elapsed_ms) do
  {:in_progress, layout, transition} -> render(layout)
  {:done, final_layout} -> render(final_layout)
end

# Bail out mid-transition
current_layout = LayoutTransition.cancel(transition)

Easing: :linear, :ease_in_out, :ease_out.

Example

mix run examples/adaptive_ui_demo.exs