feat: implement model management commands and overlays for enhanced user interaction

This commit is contained in:
asepharyana
2026-07-12 02:06:46 +07:00
parent 9f272d5973
commit 662e269f2c
8 changed files with 403 additions and 26 deletions
+46 -15
View File
@@ -62,6 +62,7 @@ pub struct InputState {
pub autocomplete_prefix: String,
pub autocomplete_candidates: Vec<String>,
pub autocomplete_idx: usize,
pub autocomplete_visible: bool,
}
const COMMANDS: &[&str] = &[
@@ -87,6 +88,9 @@ const COMMANDS: &[&str] = &[
"/login openai",
"/edit",
"/mcp add",
"/model",
"/model ls",
"/model add",
];
impl InputState {
@@ -99,36 +103,63 @@ impl InputState {
autocomplete_prefix: String::new(),
autocomplete_candidates: Vec::new(),
autocomplete_idx: 0,
autocomplete_visible: false,
}
}
pub fn tab_complete(&mut self) {
let trimmed = self.buffer.trim().to_string();
if trimmed.is_empty() {
return;
}
pub fn close_autocomplete(&mut self) {
self.autocomplete_visible = false;
self.autocomplete_candidates.clear();
self.autocomplete_prefix.clear();
self.autocomplete_idx = 0;
}
if !trimmed.starts_with('/') {
pub fn open_autocomplete(&mut self) {
let trimmed = self.buffer.trim().to_string();
if trimmed.is_empty() || !trimmed.starts_with('/') {
self.close_autocomplete();
return;
}
let prefix = trimmed.to_lowercase();
self.autocomplete_candidates = COMMANDS
.iter()
.filter(|c| c.starts_with(&prefix))
.map(|c| c.to_string())
.collect();
self.autocomplete_prefix = prefix;
self.autocomplete_idx = 0;
self.autocomplete_visible = !self.autocomplete_candidates.is_empty();
}
if prefix != self.autocomplete_prefix || self.autocomplete_candidates.is_empty() {
self.autocomplete_candidates = COMMANDS
.iter()
.filter(|c| c.starts_with(&prefix))
.map(|c| c.to_string())
.collect();
self.autocomplete_prefix = prefix;
self.autocomplete_idx = 0;
pub fn cycle_autocomplete(&mut self, forward: bool) {
let n = self.autocomplete_candidates.len();
if n == 0 { return; }
if forward {
self.autocomplete_idx = (self.autocomplete_idx + 1) % n;
} else {
self.autocomplete_idx = (self.autocomplete_idx + 1) % self.autocomplete_candidates.len();
self.autocomplete_idx = if self.autocomplete_idx == 0 { n - 1 } else { self.autocomplete_idx - 1 };
}
}
pub fn select_autocomplete(&mut self) -> bool {
if let Some(candidate) = self.autocomplete_candidates.get(self.autocomplete_idx) {
self.buffer = candidate.clone();
self.cursor = self.buffer.len();
self.close_autocomplete();
true
} else {
false
}
}
pub fn tab_complete(&mut self) {
// Legacy inline tab-complete — used as a fallback when the dropdown
// isn't visible yet. Opens the dropdown on the first Tab press.
if !self.autocomplete_visible {
self.open_autocomplete();
} else {
self.cycle_autocomplete(true);
}
}