use ratatui::Frame;
use ratatui::layout::{Alignment, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use super::super::app::{
App, ConfigFocus, ConfigRow, FallbackFocus, FallbackHint, FooterAlert, GLOBAL_CONFIG_ROWS,
GlobalConfigRow, StatusFocus, Tab, TokenView, config_rows, fallback_hint,
};
use super::super::theme;
const TAB_NAV: (&str, &str) = ("←→", "tabs");
pub(super) fn draw(frame: &mut Frame<'_>, area: Rect, app: &App) {
let area = inset_x(area, 1);
if let Some(alert) = &app.footer_alert {
draw_alert(frame, area, alert);
return;
}
let has_sub_focus = (app.tab == Tab::Setup && app.config_focus == ConfigFocus::Actions)
|| (app.tab == Tab::Fallback && app.fallback_focus == FallbackFocus::Detail)
|| (app.tab == Tab::Status && app.status.focus == StatusFocus::Detail)
|| (app.tab == Tab::Tokens && app.token_view == TokenView::Models);
let q_label: &str = if has_sub_focus { "back" } else { "quit" };
let tail: &[(&str, &str)] = match app.tab {
Tab::Overview => &[("⇧↑↓", "reorder"), ("a", "actions"), ("?", "help")],
Tab::Usage => &[
("↑↓", "account"),
("r", "refresh account"),
("a", "actions"),
("?", "help"),
],
Tab::Tokens => match app.token_view {
TokenView::Dashboard => &[
("↵", "models"),
("r", "reload"),
("c", "count cache"),
("?", "help"),
],
TokenView::Models => &[("↑↓", "model"), ("c", "count cache"), ("?", "help")],
},
Tab::Setup => match app.config_focus {
ConfigFocus::Profiles => &[
("↑↓", "account"),
("↵", "configure"),
("n", "new"),
("a", "actions"),
("?", "help"),
],
ConfigFocus::Actions => {
match config_rows(app).get(app.config_action_cursor) {
Some(ConfigRow::Model) => &[
("↑↓", "row"),
("space", "cycle"),
("↵", "custom"),
("a", "actions"),
("?", "help"),
],
Some(ConfigRow::EnvEntry(_)) => &[
("↑↓", "row"),
("↵", "edit value"),
("a", "actions"),
("?", "help"),
],
Some(ConfigRow::EnvAdd) => &[
("↑↓", "row"),
("↵", "add env"),
("a", "actions"),
("?", "help"),
],
Some(ConfigRow::ModelOverrideAdd) => {
&[("↑↓", "row"), ("↵", "add override"), ("?", "help")]
}
_ => &[
("↑↓", "row"),
("↵", "edit / toggle"),
("a", "actions"),
("?", "help"),
],
}
}
},
Tab::Config => {
if app.refresh_interval_draft.is_some() {
&[("↵", "save"), ("←→", "caret"), ("esc", "cancel")]
} else if GLOBAL_CONFIG_ROWS
.get(app.global_config_cursor)
.is_some_and(|r| *r == GlobalConfigRow::RefreshInterval)
{
&[
("↑↓", "row"),
("space", "cycle"),
("↵", "custom"),
("?", "help"),
]
} else {
&[("↑↓", "row"), ("space/↵", "cycle / toggle"), ("?", "help")]
}
}
Tab::Status => match app.status.focus {
StatusFocus::List => &[
("↑↓", "incident"),
("↵", "open"),
("r", "refresh"),
("a", "actions"),
("?", "help"),
],
StatusFocus::Detail => &[("↑↓", "scroll"), ("a", "actions"), ("?", "help")],
},
Tab::Fallback => match fallback_hint(app) {
FallbackHint::Empty => &[("?", "help")],
FallbackHint::ChainMember => &[
("↑↓", "move"),
("⇧↑↓", "reorder = priority"),
("↵", "open"),
("a", "actions"),
("?", "help"),
],
FallbackHint::ChainAdd => &[("↑↓", "move"), ("↵", "add"), ("?", "help")],
FallbackHint::DetailThreshold => &[
("↑↓", "row"),
("+", "raise"),
("-", "lower"),
("↵", "type"),
("a", "actions"),
("?", "help"),
],
FallbackHint::DetailThresholdEdit => {
&[("↵", "save"), ("←→", "caret"), ("esc", "cancel")]
}
FallbackHint::DetailRemove => &[
("↑↓", "row"),
("↵", "remove"),
("a", "actions"),
("?", "help"),
],
FallbackHint::DetailRemoveArmed => {
&[("↵", "confirm remove"), ("esc", "cancel"), ("?", "help")]
}
FallbackHint::DetailAdd => &[("↑↓", "pick"), ("↵", "add"), ("?", "help")],
},
};
let show_q = !((app.tab == Tab::Fallback
&& matches!(
fallback_hint(app),
FallbackHint::DetailThresholdEdit | FallbackHint::DetailRemoveArmed
))
|| (app.tab == Tab::Config && app.refresh_interval_draft.is_some()));
let mut hints: Vec<(&str, &str)> = std::iter::once(TAB_NAV)
.chain(tail.iter().copied())
.collect();
if show_q {
hints.push(("q", q_label));
}
let mut spans: Vec<Span<'_>> = Vec::new();
for (i, (key, label)) in hints.iter().enumerate() {
if i > 0 {
spans.push(Span::styled(" ", theme::faint()));
}
spans.push(Span::styled(
*key,
theme::accent().add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(" "));
spans.push(Span::styled(*label, theme::dim()));
}
frame.render_widget(
Paragraph::new(Line::from(spans))
.style(theme::base())
.alignment(Alignment::Left),
area,
);
}
fn inset_x(area: Rect, pad: u16) -> Rect {
Rect {
x: area.x.saturating_add(pad),
width: area.width.saturating_sub(pad.saturating_mul(2)),
..area
}
}
fn draw_alert(frame: &mut Frame<'_>, area: Rect, alert: &FooterAlert) {
let FooterAlert::Warn(msg) = alert;
let spans = vec![
Span::styled("! ", Style::default().fg(theme::warning_color())),
Span::styled(msg.as_str(), theme::dim()),
];
frame.render_widget(
Paragraph::new(Line::from(spans))
.style(theme::base())
.alignment(Alignment::Left),
area,
);
}