feat(settings/review): allow selecting review model

This commit is contained in:
James Peter
2025-11-13 20:58:41 +10:00
parent 7d8684e174
commit 2134f3e6b7
10 changed files with 341 additions and 29 deletions
+1 -1
View File
@@ -4969,7 +4969,7 @@ async fn spawn_review_thread(
let mut review_config = (*config).clone();
review_config.model = review_model.clone();
review_config.model_family = review_family.clone();
review_config.model_reasoning_effort = ReasoningEffortConfig::Low;
review_config.model_reasoning_effort = config.review_model_reasoning_effort;
review_config.model_reasoning_summary = ReasoningSummaryConfig::Detailed;
review_config.model_text_verbosity = config.model_text_verbosity;
review_config.user_instructions = None;
+61 -2
View File
@@ -67,7 +67,7 @@ use toml_edit::Table as TomlTable;
use which::which;
const OPENAI_DEFAULT_MODEL: &str = "gpt-5-codex";
const OPENAI_DEFAULT_REVIEW_MODEL: &str = "gpt-5-codex";
const OPENAI_DEFAULT_REVIEW_MODEL: &str = "gpt-5-codex-mini";
pub const GPT_5_CODEX_MEDIUM_MODEL: &str = "gpt-5-codex";
/// Maximum number of bytes of the documentation that will be embedded. Larger
@@ -104,9 +104,12 @@ pub struct Config {
/// Optional override of model selection.
pub model: String,
/// Model used specifically for review sessions. Defaults to "gpt-5-codex".
/// Model used specifically for review sessions. Defaults to "gpt-5-codex-mini".
pub review_model: String,
/// Reasoning effort used when running review sessions.
pub review_model_reasoning_effort: ReasoningEffort,
pub model_family: ModelFamily,
/// Size of the context window for the model, in tokens.
@@ -991,6 +994,37 @@ pub fn set_tui_review_auto_resolve(code_home: &Path, enabled: bool) -> anyhow::R
Ok(())
}
/// Persist the review model + reasoning effort into `CODEX_HOME/config.toml`.
pub fn set_review_model(
code_home: &Path,
model: &str,
effort: ReasoningEffort,
) -> anyhow::Result<()> {
let trimmed = model.trim();
if trimmed.is_empty() {
return Err(anyhow::anyhow!("review model cannot be empty"));
}
let config_path = code_home.join(CONFIG_TOML_FILE);
let read_path = resolve_code_path_for_read(code_home, Path::new(CONFIG_TOML_FILE));
let mut doc = match std::fs::read_to_string(&read_path) {
Ok(contents) => contents.parse::<DocumentMut>()?,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
Err(e) => return Err(e.into()),
};
doc["review_model"] = toml_edit::value(trimmed);
doc["review_model_reasoning_effort"] =
toml_edit::value(effort.to_string().to_ascii_lowercase());
std::fs::create_dir_all(code_home)?;
let tmp_file = NamedTempFile::new_in(code_home)?;
std::fs::write(tmp_file.path(), doc.to_string())?;
tmp_file.persist(config_path)?;
Ok(())
}
/// Persist Auto Drive defaults under `[auto_drive]`.
pub fn set_auto_drive_settings(
code_home: &Path,
@@ -1639,6 +1673,8 @@ pub struct ConfigToml {
pub model: Option<String>,
/// Review model override used by the `/review` feature.
pub review_model: Option<String>,
/// Reasoning effort override used for the review model.
pub review_model_reasoning_effort: Option<ReasoningEffort>,
/// Provider to use from the model_providers map.
pub model_provider: Option<String>,
@@ -2289,12 +2325,19 @@ impl Config {
// Default review model when not set in config; allow CLI override to take precedence.
let review_model = override_review_model
.or(config_profile.review_model.clone())
.or(cfg.review_model)
.unwrap_or_else(default_review_model);
let review_model_reasoning_effort = config_profile
.review_model_reasoning_effort
.or(cfg.review_model_reasoning_effort)
.unwrap_or(ReasoningEffort::High);
let config = Self {
model,
review_model,
review_model_reasoning_effort,
model_family,
model_context_window,
model_max_output_tokens,
@@ -3192,6 +3235,10 @@ model_verbosity = "high"
)?;
assert_eq!("o3", o3_profile_config.model);
assert_eq!(OPENAI_DEFAULT_REVIEW_MODEL, o3_profile_config.review_model);
assert_eq!(
ReasoningEffort::High,
o3_profile_config.review_model_reasoning_effort
);
assert_eq!(
find_family_for_model("o3").expect("known model slug"),
o3_profile_config.model_family
@@ -3242,6 +3289,10 @@ model_verbosity = "high"
)?;
assert_eq!("gpt-3.5-turbo", gpt3_profile_config.model);
assert_eq!(OPENAI_DEFAULT_REVIEW_MODEL, gpt3_profile_config.review_model);
assert_eq!(
ReasoningEffort::High,
gpt3_profile_config.review_model_reasoning_effort
);
assert_eq!(
find_family_for_model("gpt-3.5-turbo").expect("known model slug"),
gpt3_profile_config.model_family
@@ -3307,6 +3358,10 @@ model_verbosity = "high"
)?;
assert_eq!("o3", zdr_profile_config.model);
assert_eq!(OPENAI_DEFAULT_REVIEW_MODEL, zdr_profile_config.review_model);
assert_eq!(
ReasoningEffort::High,
zdr_profile_config.review_model_reasoning_effort
);
assert_eq!(
find_family_for_model("o3").expect("known model slug"),
zdr_profile_config.model_family
@@ -3348,6 +3403,10 @@ model_verbosity = "high"
)?;
assert_eq!("gpt-5", gpt5_profile_config.model);
assert_eq!(OPENAI_DEFAULT_REVIEW_MODEL, gpt5_profile_config.review_model);
assert_eq!(
ReasoningEffort::High,
gpt5_profile_config.review_model_reasoning_effort
);
assert_eq!(
find_family_for_model("gpt-5").expect("known model slug"),
gpt5_profile_config.model_family
+2
View File
@@ -11,12 +11,14 @@ use crate::protocol::AskForApproval;
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
pub struct ConfigProfile {
pub model: Option<String>,
pub review_model: Option<String>,
/// The key in the `model_providers` map identifying the
/// [`ModelProviderInfo`] to use.
pub model_provider: Option<String>,
pub approval_policy: Option<AskForApproval>,
pub disable_response_storage: Option<bool>,
pub model_reasoning_effort: Option<ReasoningEffort>,
pub review_model_reasoning_effort: Option<ReasoningEffort>,
pub model_reasoning_summary: Option<ReasoningSummary>,
pub model_text_verbosity: Option<TextVerbosity>,
pub chatgpt_base_url: Option<String>,
+10
View File
@@ -2277,6 +2277,11 @@ impl App<'_> {
widget.apply_model_selection(model, effort);
}
}
AppEvent::UpdateReviewModelSelection { model, effort } => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.apply_review_model_selection(model, effort);
}
}
AppEvent::UpdateTextVerbosity(new_verbosity) => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.set_text_verbosity(new_verbosity);
@@ -2383,6 +2388,11 @@ impl App<'_> {
widget.set_review_auto_resolve_attempts(attempts);
}
}
AppEvent::ShowReviewModelSelector => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.show_review_model_selector();
}
}
AppEvent::RunReviewWithScope {
prompt,
hint,
+8
View File
@@ -208,6 +208,12 @@ pub(crate) enum AppEvent {
effort: Option<ReasoningEffort>,
},
/// Update the dedicated review model + reasoning effort
UpdateReviewModelSelection {
model: String,
effort: ReasoningEffort,
},
/// Update the text verbosity level
UpdateTextVerbosity(TextVerbosity),
@@ -219,6 +225,8 @@ pub(crate) enum AppEvent {
UpdateReviewAutoResolveEnabled(bool),
/// Set the maximum number of Auto Resolve re-review attempts
UpdateReviewAutoResolveAttempts(u32),
/// Open the review model selector overlay
ShowReviewModelSelector,
/// Enable/disable a specific validation tool
UpdateValidationTool { name: String, enable: bool },
/// Enable/disable an entire validation group
+10 -3
View File
@@ -33,7 +33,7 @@ mod file_search_popup;
mod paste_burst;
mod popup_consts;
pub(crate) mod agent_editor_view;
mod model_selection_view;
pub(crate) mod model_selection_view;
mod scroll_state;
mod selection_popup_common;
pub mod list_selection_view;
@@ -95,7 +95,7 @@ use code_common::model_presets::ModelPreset;
use code_core::config_types::ReasoningEffort;
use code_core::config_types::TextVerbosity;
use code_core::config_types::ThemeName;
pub(crate) use model_selection_view::ModelSelectionView;
pub(crate) use model_selection_view::{ModelSelectionTarget, ModelSelectionView};
pub(crate) use mcp_settings_view::McpSettingsView;
pub(crate) use theme_selection_view::ThemeSelectionView;
use verbosity_selection_view::VerbositySelectionView;
@@ -678,8 +678,15 @@ impl BottomPane<'_> {
presets: Vec<ModelPreset>,
current_model: String,
current_effort: ReasoningEffort,
target: ModelSelectionTarget,
) {
let view = ModelSelectionView::new(presets, current_model, current_effort, self.app_event_tx.clone());
let view = ModelSelectionView::new(
presets,
current_model,
current_effort,
target,
self.app_event_tx.clone(),
);
self.active_view = Some(Box::new(view));
self.active_view_kind = ActiveViewKind::Other;
// Status shown in composer title now
@@ -17,6 +17,35 @@ use std::cmp::Ordering;
use super::settings_panel::{render_panel, PanelFrameStyle};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum ModelSelectionTarget {
Session,
Review,
}
impl ModelSelectionTarget {
fn panel_title(self) -> &'static str {
match self {
ModelSelectionTarget::Session => "Select Model & Reasoning",
ModelSelectionTarget::Review => "Select Review Model & Reasoning",
}
}
fn current_label(self) -> &'static str {
match self {
ModelSelectionTarget::Session => "Current model",
ModelSelectionTarget::Review => "Review model",
}
}
fn reasoning_label(self) -> &'static str {
match self {
ModelSelectionTarget::Session => "Reasoning effort",
ModelSelectionTarget::Review => "Review reasoning",
}
}
}
pub(crate) struct ModelSelectionView {
presets: Vec<ModelPreset>,
selected_index: usize,
@@ -24,15 +53,15 @@ pub(crate) struct ModelSelectionView {
current_effort: ReasoningEffort,
app_event_tx: AppEventSender,
is_complete: bool,
target: ModelSelectionTarget,
}
impl ModelSelectionView {
const PANEL_TITLE: &'static str = "Select Model & Reasoning";
pub fn new(
presets: Vec<ModelPreset>,
current_model: String,
current_effort: ReasoningEffort,
target: ModelSelectionTarget,
app_event_tx: AppEventSender,
) -> Self {
let initial_index = Self::initial_selection(&presets, &current_model, current_effort);
@@ -43,6 +72,7 @@ impl ModelSelectionView {
current_effort,
app_event_tx,
is_complete: false,
target,
}
}
@@ -148,10 +178,20 @@ impl ModelSelectionView {
fn confirm_selection(&mut self) {
if let Some(preset) = self.presets.get(self.selected_index) {
let effort = Self::preset_effort(preset);
let _ = self.app_event_tx.send(AppEvent::UpdateModelSelection {
model: preset.model.to_string(),
effort: Some(effort),
});
match self.target {
ModelSelectionTarget::Session => {
let _ = self.app_event_tx.send(AppEvent::UpdateModelSelection {
model: preset.model.to_string(),
effort: Some(effort),
});
}
ModelSelectionTarget::Review => {
let _ = self.app_event_tx.send(AppEvent::UpdateReviewModelSelection {
model: preset.model.to_string(),
effort,
});
}
}
}
self.is_complete = true;
}
@@ -324,7 +364,7 @@ impl ModelSelectionView {
let mut lines: Vec<Line> = Vec::new();
lines.push(Line::from(vec![
Span::styled(
"Current model: ",
format!("{}: ", self.target.current_label()),
Style::default().fg(crate::colors::text_dim()),
),
Span::styled(
@@ -336,7 +376,7 @@ impl ModelSelectionView {
]));
lines.push(Line::from(vec![
Span::styled(
"Reasoning effort: ",
format!("{}: ", self.target.reasoning_label()),
Style::default().fg(crate::colors::text_dim()),
),
Span::styled(
@@ -479,7 +519,7 @@ impl<'a> BottomPaneView<'a> for ModelSelectionView {
render_panel(
area,
buf,
Self::PANEL_TITLE,
self.target.panel_title(),
PanelFrameStyle::bottom_pane(),
|inner, buf| self.render_panel_body(inner, buf),
);
@@ -1,4 +1,4 @@
use code_core::config_types::AutoResolveAttemptLimit;
use code_core::config_types::{AutoResolveAttemptLimit, ReasoningEffort};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::buffer::Buffer;
use ratatui::layout::{Alignment, Rect};
@@ -19,16 +19,20 @@ const DEFAULT_VISIBLE_ROWS: usize = 4;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum SelectionKind {
Model,
Toggle,
Attempts,
}
enum RowData {
Model,
Toggle,
Attempts,
}
pub(crate) struct ReviewSettingsView {
review_model: String,
review_reasoning: ReasoningEffort,
auto_resolve_enabled: bool,
auto_resolve_attempts: u32,
auto_attempt_index: usize,
@@ -40,7 +44,14 @@ pub(crate) struct ReviewSettingsView {
}
impl ReviewSettingsView {
pub fn set_review_model(&mut self, model: String, effort: ReasoningEffort) {
self.review_model = model;
self.review_reasoning = effort;
}
pub fn new(
review_model: String,
review_reasoning: ReasoningEffort,
auto_resolve_enabled: bool,
auto_resolve_attempts: u32,
app_event_tx: AppEventSender,
@@ -56,6 +67,8 @@ impl ReviewSettingsView {
.position(|&value| value == auto_resolve_attempts)
.unwrap_or(default_index);
Self {
review_model,
review_reasoning,
auto_resolve_enabled,
auto_resolve_attempts,
auto_attempt_index: attempt_index,
@@ -99,10 +112,19 @@ impl ReviewSettingsView {
.send(AppEvent::UpdateReviewAutoResolveAttempts(self.auto_resolve_attempts));
}
fn open_review_model_selector(&self) {
self.app_event_tx
.send(AppEvent::ShowReviewModelSelector);
}
fn build_rows(&self) -> (Vec<RowData>, Vec<usize>, Vec<SelectionKind>) {
let rows = vec![RowData::Toggle, RowData::Attempts];
let selection_rows = vec![0, 1];
let selection_kinds = vec![SelectionKind::Toggle, SelectionKind::Attempts];
let rows = vec![RowData::Model, RowData::Toggle, RowData::Attempts];
let selection_rows = vec![0, 1, 2];
let selection_kinds = vec![
SelectionKind::Model,
SelectionKind::Toggle,
SelectionKind::Attempts,
];
(rows, selection_rows, selection_kinds)
}
@@ -115,6 +137,16 @@ impl ReviewSettingsView {
target.clamp(1, total)
}
fn reasoning_label(effort: ReasoningEffort) -> &'static str {
match effort {
ReasoningEffort::High => "High",
ReasoningEffort::Medium => "Medium",
ReasoningEffort::Low => "Low",
ReasoningEffort::Minimal => "Minimal",
ReasoningEffort::None => "None",
}
}
fn render_row(&self, row: &RowData, selected: bool) -> Line<'static> {
let arrow = if selected { "› " } else { " " };
let arrow_style = if selected {
@@ -123,6 +155,43 @@ impl ReviewSettingsView {
Style::default().fg(colors::text_dim())
};
match row {
RowData::Model => {
let label_style = if selected {
Style::default()
.fg(colors::primary())
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(colors::text()).add_modifier(Modifier::BOLD)
};
let value_style = if selected {
Style::default()
.fg(colors::function())
.add_modifier(Modifier::BOLD)
} else {
Style::default().fg(colors::text())
};
let model_label = self.review_model.trim();
let model_display = if model_label.is_empty() {
"(not set)"
} else {
model_label
};
let value_text = format!(
"{} · {} reasoning",
model_display,
Self::reasoning_label(self.review_reasoning)
);
let mut spans = vec![
Span::styled(arrow, arrow_style),
Span::styled("Review model", label_style),
Span::raw(" "),
Span::styled(value_text, value_style),
];
if selected {
spans.push(Span::raw(" (Enter to change)"));
}
Line::from(spans)
}
RowData::Toggle => {
let label_style = if selected {
Style::default()
@@ -228,6 +297,7 @@ impl ReviewSettingsView {
match kind {
SelectionKind::Toggle => self.toggle_auto_resolve(),
SelectionKind::Attempts => self.adjust_auto_resolve_attempts(false),
SelectionKind::Model => {}
}
}
}
@@ -236,6 +306,7 @@ impl ReviewSettingsView {
match kind {
SelectionKind::Toggle => self.toggle_auto_resolve(),
SelectionKind::Attempts => self.adjust_auto_resolve_attempts(true),
SelectionKind::Model => {}
}
}
}
@@ -245,6 +316,7 @@ impl ReviewSettingsView {
match kind {
SelectionKind::Toggle => self.toggle_auto_resolve(),
SelectionKind::Attempts => self.adjust_auto_resolve_attempts(true),
SelectionKind::Model => self.open_review_model_selector(),
}
}
}
@@ -296,11 +368,11 @@ impl<'a> BottomPaneView<'a> for ReviewSettingsView {
let header_lines = vec![
Line::from(Span::styled(
"Adjust Auto Resolve automation for /review.",
"Choose review model + Auto Resolve automation for /review.",
Style::default().fg(colors::text_dim()),
)),
Line::from(Span::styled(
"Use ↑↓ to navigate · Enter/Space toggle · ←→ adjust values · Esc close",
"Use ↑↓ to navigate · Enter select/open · Space toggle · ←→ adjust values · Esc close",
Style::default().fg(colors::text_dim()),
)),
Line::from(""),
@@ -310,7 +382,7 @@ impl<'a> BottomPaneView<'a> for ReviewSettingsView {
Span::styled("↑↓", Style::default().fg(colors::function())),
Span::styled(" Navigate ", Style::default().fg(colors::text_dim())),
Span::styled("Enter", Style::default().fg(colors::success())),
Span::styled(" Toggle ", Style::default().fg(colors::text_dim())),
Span::styled(" Select ", Style::default().fg(colors::text_dim())),
Span::styled("Space", Style::default().fg(colors::success())),
Span::styled(" Toggle ", Style::default().fg(colors::text_dim())),
Span::styled("←→", Style::default().fg(colors::function())),
+112 -7
View File
@@ -878,6 +878,7 @@ use crate::bottom_pane::list_selection_view::{ListSelectionView, SelectionItem};
use crate::bottom_pane::CloudTasksView;
use crate::bottom_pane::validation_settings_view;
use crate::bottom_pane::validation_settings_view::{GroupStatus, ToolRow};
use crate::bottom_pane::model_selection_view::ModelSelectionTarget;
use crate::bottom_pane::BottomPane;
use crate::bottom_pane::BottomPaneParams;
use crate::bottom_pane::{UndoTimelineEntry, UndoTimelineEntryKind, UndoTimelineView};
@@ -13721,7 +13722,13 @@ impl ChatWidget<'_> {
fn build_review_settings_content(&mut self) -> ReviewSettingsContent {
let auto_resolve_enabled = self.config.tui.review_auto_resolve;
let attempts = self.configured_auto_resolve_re_reviews();
let view = ReviewSettingsView::new(auto_resolve_enabled, attempts, self.app_event_tx.clone());
let view = ReviewSettingsView::new(
self.config.review_model.clone(),
self.config.review_model_reasoning_effort,
auto_resolve_enabled,
attempts,
self.app_event_tx.clone(),
);
ReviewSettingsContent::new(view)
}
@@ -18572,6 +18579,24 @@ Have we met every part of this goal and is there no further work to do?"#
presets,
self.config.model.clone(),
self.config.model_reasoning_effort,
ModelSelectionTarget::Session,
);
}
pub(crate) fn show_review_model_selector(&mut self) {
let presets = self.available_model_presets();
if presets.is_empty() {
self.bottom_pane.flash_footer_notice(
"No model presets are available for review. Update configuration to define models."
.to_string(),
);
return;
}
self.bottom_pane.show_model_selection(
presets,
self.config.review_model.clone(),
self.config.review_model_reasoning_effort,
ModelSelectionTarget::Review,
);
}
@@ -18632,6 +18657,66 @@ Have we met every part of this goal and is there no further work to do?"#
self.request_redraw();
}
pub(crate) fn apply_review_model_selection(
&mut self,
model: String,
effort: ReasoningEffort,
) {
let trimmed = model.trim();
if trimmed.is_empty() {
return;
}
let mut updated = false;
if !self.config.review_model.eq_ignore_ascii_case(trimmed) {
self.config.review_model = trimmed.to_string();
updated = true;
}
if self.config.review_model_reasoning_effort != effort {
self.config.review_model_reasoning_effort = effort;
updated = true;
}
if !updated {
self.bottom_pane
.flash_footer_notice("Review model unchanged.".to_string());
return;
}
let message = if let Ok(home) = code_core::config::find_code_home() {
match code_core::config::set_review_model(
&home,
&self.config.review_model,
self.config.review_model_reasoning_effort,
) {
Ok(_) => format!(
"Review model set to {} ({} reasoning)",
self.config.review_model,
Self::format_reasoning_effort(self.config.review_model_reasoning_effort)
),
Err(err) => {
tracing::warn!("Failed to persist review model: {err}");
format!(
"Review model set for this session (failed to persist): {}",
self.config.review_model
)
}
}
} else {
tracing::warn!("Could not locate Code home to persist review model");
format!(
"Review model set for this session: {}",
self.config.review_model
)
};
self.bottom_pane.flash_footer_notice(message);
self.refresh_settings_overview_rows();
self.update_review_settings_model_row();
self.request_redraw();
}
pub(crate) fn handle_reasoning_command(&mut self, command_args: String) {
// command_args contains only the arguments after the command (e.g., "high" not "/reasoning high")
let trimmed = command_args.trim();
@@ -18666,11 +18751,12 @@ Have we met every part of this goal and is there no further work to do?"#
return;
}
self.bottom_pane.show_model_selection(
presets,
self.config.model.clone(),
self.config.model_reasoning_effort,
);
self.bottom_pane.show_model_selection(
presets,
self.config.model.clone(),
self.config.model_reasoning_effort,
ModelSelectionTarget::Session,
);
return;
}
}
@@ -19027,6 +19113,7 @@ Have we met every part of this goal and is there no further work to do?"#
presets,
current_model,
current_effort,
ModelSelectionTarget::Session,
self.app_event_tx.clone(),
);
ModelSettingsContent::new(view)
@@ -19444,7 +19531,14 @@ Have we met every part of this goal and is there no further work to do?"#
} else {
format!("Auto Resolve: On (max {} re-reviews)", attempts)
};
Some(auto_label)
let model = self.config.review_model.trim();
let model_display = if model.is_empty() { "(not set)" } else { model };
let model_part = format!(
"Model: {} ({})",
model_display,
Self::format_reasoning_effort(self.config.review_model_reasoning_effort)
);
Some(format!("{} · {}", model_part, auto_label))
}
fn settings_summary_github(&self) -> Option<String> {
@@ -27483,6 +27577,17 @@ impl ChatWidget<'_> {
self.request_redraw();
}
fn update_review_settings_model_row(&mut self) {
if let Some(overlay) = self.settings.overlay.as_mut() {
if let Some(content) = overlay.review_content_mut() {
content.update_review_model(
self.config.review_model.clone(),
self.config.review_model_reasoning_effort,
);
}
}
}
pub(crate) fn show_review_commit_loading(&mut self) {
let loading_item = SelectionItem {
name: "Loading recent commits…".to_string(),
@@ -29,6 +29,7 @@ use crate::chrome_launch::{ChromeLaunchOption, CHROME_LAUNCH_CHOICES};
use super::limits_overlay::{LimitsOverlay, LimitsOverlayContent};
use crate::live_wrap::take_prefix_by_width;
use crate::util::buffer::fill_rect;
use code_core::config_types::ReasoningEffort;
const LABEL_COLUMN_WIDTH: usize = 18;
@@ -273,6 +274,10 @@ impl ReviewSettingsContent {
pub(crate) fn new(view: ReviewSettingsView) -> Self {
Self { view }
}
pub(crate) fn update_review_model(&mut self, model: String, effort: ReasoningEffort) {
self.view.set_review_model(model, effort);
}
}
impl SettingsContent for ReviewSettingsContent {
@@ -1305,6 +1310,10 @@ impl SettingsOverlayView {
self.agents_content.as_mut()
}
pub(crate) fn review_content_mut(&mut self) -> Option<&mut ReviewSettingsContent> {
self.review_content.as_mut()
}
pub(crate) fn limits_content_mut(&mut self) -> Option<&mut LimitsSettingsContent> {
self.limits_content.as_mut()
}