Adaptive UI
View SourceThe 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