feat: implement model management commands and overlays for enhanced user interaction
This commit is contained in:
+146
@@ -558,10 +558,156 @@ fn render_overlay(frame: &mut Frame, area: Rect, overlay: crate::app::state::typ
|
||||
.block(block);
|
||||
frame.render_widget(paragraph, overlay_area);
|
||||
}
|
||||
crate::app::state::types::Overlay::ModelSelector => {
|
||||
let block = block.title(" Model Selector ");
|
||||
let mut lines: Vec<Line> = vec![
|
||||
Line::from(Span::styled(
|
||||
format!("Current: {} / {}", state.settings.provider, state.settings.model),
|
||||
Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
Line::from(Span::styled("", Style::default())),
|
||||
Line::from(Span::styled("Providers:", Style::default().fg(Theme::DIM))),
|
||||
];
|
||||
let providers: Vec<(&String, &crate::model::app_config::ProviderConfig)> = state.app_config.providers.iter().collect();
|
||||
for (i, (name, cfg)) in providers.iter().enumerate() {
|
||||
let is_current = *name == &state.settings.provider;
|
||||
let is_selected = i == state.misc.selected_index;
|
||||
let prefix = if is_selected { "▸ " } else { " " };
|
||||
let model_str = cfg.default_model.as_deref().unwrap_or("(any)");
|
||||
let label = format!("{}{} ({})", prefix, name, model_str);
|
||||
let style = if is_current {
|
||||
Style::default().fg(Theme::HIGHLIGHT).add_modifier(Modifier::BOLD)
|
||||
} else if is_selected {
|
||||
Style::default().fg(Theme::BG).bg(Theme::HIGHLIGHT)
|
||||
} else {
|
||||
Style::default().fg(Theme::TEXT)
|
||||
};
|
||||
lines.push(Line::from(Span::styled(label, style)));
|
||||
}
|
||||
lines.push(Line::from(Span::styled("", Style::default())));
|
||||
lines.push(Line::from(Span::styled(
|
||||
"↑↓ navigate · Enter select · Esc close · /model add <name> <url> to add",
|
||||
Style::default().fg(Theme::DIM),
|
||||
)));
|
||||
let paragraph = Paragraph::new(lines).block(block);
|
||||
frame.render_widget(paragraph, overlay_area);
|
||||
}
|
||||
crate::app::state::types::Overlay::ClearConfirm => {
|
||||
let block = block.title(" Clear Transcript ");
|
||||
let lines = vec![
|
||||
Line::from(Span::styled(
|
||||
"Clear all messages from the transcript?",
|
||||
Style::default().fg(Theme::TEXT),
|
||||
)),
|
||||
Line::from(Span::styled("", Style::default())),
|
||||
Line::from(Span::styled(
|
||||
"Enter to confirm · Esc to cancel",
|
||||
Style::default().fg(Theme::DIM),
|
||||
)),
|
||||
];
|
||||
let paragraph = Paragraph::new(lines).block(block);
|
||||
frame.render_widget(paragraph, overlay_area);
|
||||
}
|
||||
crate::app::state::types::Overlay::ModePicker => {
|
||||
let block = block.title(" Mode ");
|
||||
let modes = [
|
||||
("Auto", "auto-approve tools"),
|
||||
("Normal", "ask before risky tools"),
|
||||
("Plan", "plan-only mode"),
|
||||
("Yolo", "full auto"),
|
||||
];
|
||||
let current = state.mode.name();
|
||||
let mut lines: Vec<Line> = vec![
|
||||
Line::from(Span::styled(
|
||||
format!("Current: {}", current),
|
||||
Style::default().fg(Theme::INFO).add_modifier(Modifier::BOLD),
|
||||
)),
|
||||
Line::from(Span::styled("", Style::default())),
|
||||
];
|
||||
for (i, (name, desc)) in modes.iter().enumerate() {
|
||||
let is_selected = i == state.misc.selected_index;
|
||||
let prefix = if is_selected { "▸ " } else { " " };
|
||||
let label = format!("{}{} ({})", prefix, name, desc);
|
||||
let style = if *name == current {
|
||||
Style::default().fg(Theme::HIGHLIGHT).add_modifier(Modifier::BOLD).add_modifier(Modifier::ITALIC)
|
||||
} else if is_selected {
|
||||
Style::default().fg(Theme::BG).bg(Theme::HIGHLIGHT)
|
||||
} else {
|
||||
Style::default().fg(Theme::TEXT)
|
||||
};
|
||||
lines.push(Line::from(Span::styled(label, style)));
|
||||
}
|
||||
lines.push(Line::from(Span::styled("", Style::default())));
|
||||
lines.push(Line::from(Span::styled(
|
||||
"↑↓ navigate · Enter select · Esc close",
|
||||
Style::default().fg(Theme::DIM),
|
||||
)));
|
||||
let paragraph = Paragraph::new(lines).block(block);
|
||||
frame.render_widget(paragraph, overlay_area);
|
||||
}
|
||||
crate::app::state::types::Overlay::LoginPicker => {
|
||||
let block = block.title(" Login ");
|
||||
let providers = crate::app::mode::onboard_provider::PROVIDERS;
|
||||
let mut lines: Vec<Line> = vec![
|
||||
Line::from(Span::styled(
|
||||
"Select a provider to log in:",
|
||||
Style::default().fg(Theme::TEXT),
|
||||
)),
|
||||
Line::from(Span::styled("", Style::default())),
|
||||
];
|
||||
for (i, p) in providers.iter().enumerate() {
|
||||
let is_selected = i == state.misc.selected_index;
|
||||
let prefix = if is_selected { "▸ " } else { " " };
|
||||
let style = if is_selected {
|
||||
Style::default().fg(Theme::BG).bg(Theme::HIGHLIGHT)
|
||||
} else {
|
||||
Style::default().fg(Theme::TEXT)
|
||||
};
|
||||
lines.push(Line::from(Span::styled(format!("{}{}", prefix, p), style)));
|
||||
}
|
||||
lines.push(Line::from(Span::styled("", Style::default())));
|
||||
lines.push(Line::from(Span::styled(
|
||||
"↑↓ navigate · Enter select · Esc close",
|
||||
Style::default().fg(Theme::DIM),
|
||||
)));
|
||||
let paragraph = Paragraph::new(lines).block(block);
|
||||
frame.render_widget(paragraph, overlay_area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_input_bar(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
||||
// Render autocomplete dropdown if visible
|
||||
if state.input.autocomplete_visible && !state.input.autocomplete_candidates.is_empty() {
|
||||
let n = state.input.autocomplete_candidates.len().min(10) as u16;
|
||||
let dropdown_height = n + 2; // border + items
|
||||
let dropdown_area = Rect {
|
||||
x: area.x,
|
||||
y: area.y.saturating_sub(dropdown_height),
|
||||
width: area.width.min(40),
|
||||
height: dropdown_height,
|
||||
};
|
||||
let dropdown_block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(Theme::BORDER))
|
||||
.title(" Commands ");
|
||||
|
||||
let mut lines: Vec<Line> = Vec::new();
|
||||
let selected = state.input.autocomplete_idx;
|
||||
for (i, candidate) in state.input.autocomplete_candidates.iter().enumerate().take(10) {
|
||||
let prefix = if i == selected { "▸ " } else { " " };
|
||||
let style = if i == selected {
|
||||
Style::default().fg(Theme::BG).bg(Theme::HIGHLIGHT)
|
||||
} else {
|
||||
Style::default().fg(Theme::TEXT)
|
||||
};
|
||||
let label = format!("{}{}", prefix, candidate);
|
||||
lines.push(Line::from(Span::styled(label, style)));
|
||||
}
|
||||
let dropdown = Paragraph::new(lines).block(dropdown_block);
|
||||
frame.render_widget(dropdown, dropdown_area);
|
||||
}
|
||||
|
||||
let block = Block::default()
|
||||
.borders(Borders::TOP)
|
||||
.border_style(Style::default().fg(Theme::BORDER));
|
||||
|
||||
Reference in New Issue
Block a user