feat(tui) /personality (#9718)

## Summary
Adds /personality selector in the TUI, which leverages the new core
interface in #9644

Notes:
- We are doing some of our own state management for model_info loading
here, but not sure if that's ideal. open to opinions on simpler
approach, but would like to avoid blocking on a larger refactor
- Right now, the `/personality` selector just hides when the model
doesn't support it. we can update this behavior down the line

## Testing
- [x] Tested locally
- [x] Added snapshot tests
This commit is contained in:
Dylan Hurd
2026-01-25 21:59:42 -08:00
committed by GitHub
parent d27f2533a9
commit 031bafd1fb
23 changed files with 421 additions and 32 deletions

View File

@@ -23,7 +23,8 @@ pub(crate) struct GenericDisplayRow {
pub match_indices: Option<Vec<usize>>, // indices to bold (char positions)
pub description: Option<String>, // optional grey text after the name
pub disabled_reason: Option<String>, // optional disabled message
pub wrap_indent: Option<usize>, // optional indent for wrapped lines
pub is_disabled: bool,
pub wrap_indent: Option<usize>, // optional indent for wrapped lines
}
pub(crate) fn wrap_styled_line<'a>(line: &'a Line<'a>, width: u16) -> Vec<Line<'a>> {
@@ -282,13 +283,18 @@ pub(crate) fn render_rows(
}
let mut full_line = build_full_line(row, desc_col);
if Some(i) == state.selected_idx {
if Some(i) == state.selected_idx && !row.is_disabled {
// Match previous behavior: cyan + bold for the selected row.
// Reset the style first to avoid inheriting dim from keyboard shortcuts.
full_line.spans.iter_mut().for_each(|span| {
span.style = Style::default().fg(Color::Cyan).bold();
});
}
if row.is_disabled {
full_line.spans.iter_mut().for_each(|span| {
span.style = span.style.dim();
});
}
// Wrap with subsequent indent aligned to the description column.
use crate::wrapping::RtOptions;
@@ -364,11 +370,16 @@ pub(crate) fn render_rows_single_line(
}
let mut full_line = build_full_line(row, desc_col);
if Some(i) == state.selected_idx {
if Some(i) == state.selected_idx && !row.is_disabled {
full_line.spans.iter_mut().for_each(|span| {
span.style = Style::default().fg(Color::Cyan).bold();
});
}
if row.is_disabled {
full_line.spans.iter_mut().for_each(|span| {
span.style = span.style.dim();
});
}
let full_line = truncate_line_with_ellipsis_if_overflow(full_line, area.width as usize);
full_line.render(