- 1
//! Floating Human-In-The-Loop (HIL) modal dialog with in-place command editor. - 2
- 3
use ratatui::buffer::Buffer; - 4
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; - 5
use ratatui::style::Modifier; - 6
use ratatui::text::{Line, Span}; - 7
use ratatui::widgets::{Block, Borders, Clear, Paragraph, Widget}; - 8
- 9
use crate::hil::HilApprovalState; - 10
use crate::theme::Theme; - 11
- 12
pub struct HilModalView<'a> { - 13
pub state: &'a HilApprovalState, - 14
pub theme: &'a Theme, - 15
pub remember: crate::hil::RememberMode, - 16
} - 17
- 18
impl<'a> Widget for HilModalView<'a> { - 19
fn render(self, area: Rect, buf: &mut Buffer) { - 20
let modal_w = 78.min(area.width.saturating_sub(4)); - 21
let modal_h = 16.min(area.height.saturating_sub(2)); - 22
- 23
let modal_area = Rect { - 24
x: area.x + (area.width.saturating_sub(modal_w)) / 2, - 25
y: area.y + (area.height.saturating_sub(modal_h)) / 2, - 26
width: modal_w, - 27
height: modal_h, - 28
}; - 29
- 30
Clear.render(modal_area, buf); - 31
- 32
let block = Block::default() - 33
.borders(Borders::ALL) - 34
.border_style(self.theme.style_border_focus().add_modifier(Modifier::BOLD)) - 35
.title(" ! PERMISSION APPROVAL REQUIRED ") - 36
.title_style(self.theme.style_accent().add_modifier(Modifier::BOLD)) - 37
.title_alignment(Alignment::Center); - 38
- 39
let inner = block.inner(modal_area); - 40
block.render(modal_area, buf); - 41
- 42
let chunks = Layout::default() - 43
.direction(Direction::Vertical) - 44
.constraints([ - 45
Constraint::Length(4), // Metadata fields - 46
Constraint::Length(5), // Command box (or in-place editor) - 47
Constraint::Min(3), // Action key pills - 48
]) - 49
.split(inner); - 50
- 51
// 1. Metadata - 52
let meta_lines = vec![ - 53
Line::from(vec![ - 54
Span::styled( - 55
"TOOL: ", - 56
self.theme.style_card().add_modifier(Modifier::BOLD), - 57
), - 58
Span::styled( - 59
&self.state.tool_name, - 60
self.theme.style_accent().add_modifier(Modifier::BOLD), - 61
), - 62
Span::styled( - 63
" RISK: ", - 64
self.theme.style_card().add_modifier(Modifier::BOLD), - 65
), - 66
Span::styled(&self.state.risk_reason, self.theme.style_warn()), - 67
]), - 68
Line::from(vec![ - 69
Span::styled( - 70
"DIR: ", - 71
self.theme.style_card().add_modifier(Modifier::BOLD), - 72
), - 73
Span::styled(&self.state.workspace_path, self.theme.style_card()), - 74
Span::styled( - 75
format!( - 76
" COST: +${:.3} est.", - 77
self.state.cost_estimate_usd.unwrap_or(0.0) - 78
), - 79
self.theme.style_card().add_modifier(Modifier::DIM), - 80
), - 81
]), - 82
]; - 83
Paragraph::new(meta_lines).render(chunks[0], buf); - 84
- 85
// 2. Command Box - 86
let cmd_text = if self.state.is_editing { - 87
&self.state.edited_command - 88
} else { - 89
&self.state.original_command - 90
}; - 91
- 92
let cmd_title = if self.state.is_editing { - 93
" Edit Command In-Place [Enter to finish, Esc to cancel] " - 94
} else { - 95
" Proposed Shell Command " - 96
}; - 97
- 98
let cmd_border_style = if self.state.is_editing { - 99
self.theme.style_border_focus() - 100
} else { - 101
self.theme.style_border() - 102
}; - 103
- 104
let cmd_block = Block::default() - 105
.borders(Borders::ALL) - 106
.border_style(cmd_border_style) - 107
.title(cmd_title) - 108
.title_style(self.theme.style_card().add_modifier(Modifier::BOLD)); - 109
- 110
let cmd_line = if self.state.is_editing { - 111
Line::from(vec![ - 112
Span::styled( - 113
cmd_text, - 114
self.theme.style_card().add_modifier(Modifier::BOLD), - 115
), - 116
Span::styled("█", self.theme.style_accent()), - 117
]) - 118
} else { - 119
Line::from(vec![Span::styled(cmd_text, self.theme.style_card())]) - 120
}; - 121
- 122
Paragraph::new(cmd_line) - 123
.block(cmd_block) - 124
.render(chunks[1], buf); - 125
- 126
// 3. Action Pills - 127
let remember_label = match self.remember { - 128
crate::hil::RememberMode::Yes => "[r] Remember: ON", - 129
crate::hil::RememberMode::No => "[r] Remember: OFF", - 130
}; - 131
let action_spans = vec![ - 132
Span::styled( - 133
" [y] Approve Once ", - 134
self.theme.style_ok().add_modifier(Modifier::BOLD), - 135
), - 136
Span::raw(" "), - 137
Span::styled(" [a] Always for Session ", self.theme.style_accent()), - 138
Span::raw(" "), - 139
Span::styled( - 140
" [d] Deny ", - 141
self.theme.style_danger().add_modifier(Modifier::BOLD), - 142
), - 143
Span::raw(" "), - 144
Span::styled( - 145
" [e] Edit In-Place ", - 146
self.theme.style_info().add_modifier(Modifier::BOLD), - 147
), - 148
Span::raw(" "), - 149
Span::styled( - 150
remember_label, - 151
if self.remember == crate::hil::RememberMode::Yes { - 152
self.theme.style_ok().add_modifier(Modifier::BOLD) - 153
} else { - 154
self.theme.style_card().add_modifier(Modifier::DIM) - 155
}, - 156
), - 157
Span::raw(" "), - 158
Span::styled( - 159
" [Esc] Cancel ", - 160
self.theme.style_card().add_modifier(Modifier::DIM), - 161
), - 162
]; - 163
- 164
Paragraph::new(Line::from(action_spans)) - 165
.alignment(Alignment::Center) - 166
.render(chunks[2], buf); - 167
} - 168
} - 169
Indexing the workspace…
Vakyartha documentation is discovering safe artifacts, anchors, and source references.