mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
feat(settings/review): allow selecting review model
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ¤t_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())),
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user