Select thinking mode from the chat
This commit is contained in:
@@ -18,7 +18,10 @@ tool work continues concurrently.
|
|||||||
The composer status row shows the current project and, for Git repositories,
|
The composer status row shows the current project and, for Git repositories,
|
||||||
the current local branch. An unsaved draft can be moved with the project menu;
|
the current local branch. An unsaved draft can be moved with the project menu;
|
||||||
saved chats keep their original project. Use the branch menu to switch local
|
saved chats keep their original project. Use the branch menu to switch local
|
||||||
branches when that project's chats are idle.
|
branches when that project's chats are idle. The thinking menu is preselected
|
||||||
|
to the model's current mode: **Thinking** is the DS4 default, while **Direct**
|
||||||
|
answers without hidden reasoning. **Think Max** is available when the context
|
||||||
|
window is at least 393216 tokens, matching DS4's long-context requirement.
|
||||||
|
|
||||||
The **Git** pane shows added, changed, and deleted files from the current
|
The **Git** pane shows added, changed, and deleted files from the current
|
||||||
worktree, including separate staged and worktree markers. Select files to stage,
|
worktree, including separate staged and worktree markers. Select files to stage,
|
||||||
|
|||||||
29
src/app.rs
29
src/app.rs
@@ -449,6 +449,7 @@ pub(crate) enum Message {
|
|||||||
CreateSession(i32),
|
CreateSession(i32),
|
||||||
DraftProjectChanged(i32),
|
DraftProjectChanged(i32),
|
||||||
SwitchGitBranch(String),
|
SwitchGitBranch(String),
|
||||||
|
ReasoningModeChanged(ReasoningMode),
|
||||||
PermissionModeChanged(PermissionMode),
|
PermissionModeChanged(PermissionMode),
|
||||||
ToggleGitFile(PathBuf),
|
ToggleGitFile(PathBuf),
|
||||||
OpenGitDiff(PathBuf),
|
OpenGitDiff(PathBuf),
|
||||||
@@ -1522,6 +1523,34 @@ impl App {
|
|||||||
}
|
}
|
||||||
Message::DraftProjectChanged(project_id) => self.move_draft_to_project(project_id),
|
Message::DraftProjectChanged(project_id) => self.move_draft_to_project(project_id),
|
||||||
Message::SwitchGitBranch(branch) => self.switch_git_branch(&branch),
|
Message::SwitchGitBranch(branch) => self.switch_git_branch(&branch),
|
||||||
|
Message::ReasoningModeChanged(mode) => {
|
||||||
|
if self.active_chat_count() > 0 {
|
||||||
|
self.error =
|
||||||
|
Some("Stop all active generations before changing thinking mode.".into());
|
||||||
|
} else if !self
|
||||||
|
.config
|
||||||
|
.generation
|
||||||
|
.supported_reasoning_modes()
|
||||||
|
.contains(&mode)
|
||||||
|
{
|
||||||
|
self.error = Some(
|
||||||
|
"Think Max requires a context window of at least 393216 tokens.".into(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut config = self.config.clone();
|
||||||
|
config.generation.reasoning_mode = mode;
|
||||||
|
match config.save(&config_path()) {
|
||||||
|
Ok(()) => {
|
||||||
|
self.config = config;
|
||||||
|
self.preference_draft.reasoning_mode = mode;
|
||||||
|
self.error = None;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
preferences::update_runtime_config(&self.runtime_config, &self.config);
|
||||||
|
}
|
||||||
|
Err(error) => self.error = Some(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Message::PermissionModeChanged(mode) => {
|
Message::PermissionModeChanged(mode) => {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
if self.active_tools.is_some() {
|
if self.active_tools.is_some() {
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ fn optional_string<T: ToString>(value: Option<T>) -> String {
|
|||||||
value.map_or_else(String::new, |value| value.to_string())
|
value.map_or_else(String::new, |value| value.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_runtime_config(runtime_config: &RwLock<Config>, config: &Config) {
|
pub(super) fn update_runtime_config(runtime_config: &RwLock<Config>, config: &Config) {
|
||||||
*runtime_config
|
*runtime_config
|
||||||
.write()
|
.write()
|
||||||
.unwrap_or_else(|poisoned| poisoned.into_inner()) = config.clone();
|
.unwrap_or_else(|poisoned| poisoned.into_inner()) = config.clone();
|
||||||
|
|||||||
@@ -305,6 +305,19 @@ impl App {
|
|||||||
.text_size(12)
|
.text_size(12)
|
||||||
.padding([2, 6])
|
.padding([2, 6])
|
||||||
});
|
});
|
||||||
|
let reasoning_mode = self.config.generation.effective_reasoning_mode();
|
||||||
|
let reasoning_control: Element<'_, Message> = if self.active_chat_count() > 0 {
|
||||||
|
text(reasoning_mode.to_string()).size(12).into()
|
||||||
|
} else {
|
||||||
|
pick_list(
|
||||||
|
self.config.generation.supported_reasoning_modes(),
|
||||||
|
Some(reasoning_mode),
|
||||||
|
Message::ReasoningModeChanged,
|
||||||
|
)
|
||||||
|
.text_size(12)
|
||||||
|
.padding([2, 6])
|
||||||
|
.into()
|
||||||
|
};
|
||||||
composer_content =
|
composer_content =
|
||||||
composer_content.push(
|
composer_content.push(
|
||||||
row![
|
row![
|
||||||
@@ -341,6 +354,7 @@ impl App {
|
|||||||
)
|
)
|
||||||
.text_size(12)
|
.text_size(12)
|
||||||
.padding([2, 6]),
|
.padding([2, 6]),
|
||||||
|
reasoning_control,
|
||||||
icon(ICON_MODEL, 16),
|
icon(ICON_MODEL, 16),
|
||||||
text(self.config.model.to_string()).size(12),
|
text(self.config.model.to_string()).size(12),
|
||||||
action,
|
action,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub(crate) const REASONING_MODES: [ReasoningMode; 3] = [
|
|||||||
ReasoningMode::Max,
|
ReasoningMode::Max,
|
||||||
ReasoningMode::Direct,
|
ReasoningMode::Direct,
|
||||||
];
|
];
|
||||||
|
const STANDARD_REASONING_MODES: [ReasoningMode; 2] = [ReasoningMode::High, ReasoningMode::Direct];
|
||||||
const THINK_MAX_MIN_CONTEXT: i32 = 393_216;
|
const THINK_MAX_MIN_CONTEXT: i32 = 393_216;
|
||||||
const MAX_CPU_THREADS: u32 = 32;
|
const MAX_CPU_THREADS: u32 = 32;
|
||||||
const MAX_MTP_DRAFT_TOKENS: i32 = 16;
|
const MAX_MTP_DRAFT_TOKENS: i32 = 16;
|
||||||
@@ -555,6 +556,23 @@ impl Default for GenerationPreferences {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GenerationPreferences {
|
impl GenerationPreferences {
|
||||||
|
pub(crate) fn effective_reasoning_mode(&self) -> ReasoningMode {
|
||||||
|
if self.reasoning_mode == ReasoningMode::Max && self.context_tokens < THINK_MAX_MIN_CONTEXT
|
||||||
|
{
|
||||||
|
ReasoningMode::High
|
||||||
|
} else {
|
||||||
|
self.reasoning_mode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn supported_reasoning_modes(&self) -> &'static [ReasoningMode] {
|
||||||
|
if self.context_tokens >= THINK_MAX_MIN_CONTEXT {
|
||||||
|
&REASONING_MODES
|
||||||
|
} else {
|
||||||
|
&STANDARD_REASONING_MODES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn validate(&self) -> Result<(), String> {
|
pub(crate) fn validate(&self) -> Result<(), String> {
|
||||||
if self.context_tokens <= 0 {
|
if self.context_tokens <= 0 {
|
||||||
return Err("Context tokens must be a positive whole number.".into());
|
return Err("Context tokens must be a positive whole number.".into());
|
||||||
@@ -588,13 +606,7 @@ impl GenerationPreferences {
|
|||||||
top_k: 0,
|
top_k: 0,
|
||||||
stops: Vec::new(),
|
stops: Vec::new(),
|
||||||
seed: self.seed,
|
seed: self.seed,
|
||||||
reasoning_mode: if self.reasoning_mode == ReasoningMode::Max
|
reasoning_mode: self.effective_reasoning_mode(),
|
||||||
&& self.context_tokens < THINK_MAX_MIN_CONTEXT
|
|
||||||
{
|
|
||||||
ReasoningMode::High
|
|
||||||
} else {
|
|
||||||
self.reasoning_mode
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -680,6 +692,11 @@ mod tests {
|
|||||||
|
|
||||||
let glm = defaults.turn_settings(ModelChoice::Glm52, cache);
|
let glm = defaults.turn_settings(ModelChoice::Glm52, cache);
|
||||||
assert_eq!((glm.temperature, glm.top_p, glm.min_p), (1.0, 0.95, 0.0));
|
assert_eq!((glm.temperature, glm.top_p, glm.min_p), (1.0, 0.95, 0.0));
|
||||||
|
assert_eq!(
|
||||||
|
defaults.supported_reasoning_modes(),
|
||||||
|
&[ReasoningMode::High, ReasoningMode::Direct]
|
||||||
|
);
|
||||||
|
assert_eq!(defaults.effective_reasoning_mode(), ReasoningMode::High);
|
||||||
|
|
||||||
let explicit = GenerationPreferences {
|
let explicit = GenerationPreferences {
|
||||||
top_p: Some(0.4),
|
top_p: Some(0.4),
|
||||||
@@ -690,6 +707,38 @@ mod tests {
|
|||||||
let effective = explicit.turn_settings(ModelChoice::Glm52, cache);
|
let effective = explicit.turn_settings(ModelChoice::Glm52, cache);
|
||||||
assert_eq!((effective.top_p, effective.min_p), (0.4, 0.2));
|
assert_eq!((effective.top_p, effective.min_p), (0.4, 0.2));
|
||||||
assert_eq!(effective.reasoning_mode, ReasoningMode::High);
|
assert_eq!(effective.reasoning_mode, ReasoningMode::High);
|
||||||
|
|
||||||
|
let long_context = GenerationPreferences {
|
||||||
|
context_tokens: THINK_MAX_MIN_CONTEXT,
|
||||||
|
reasoning_mode: ReasoningMode::Max,
|
||||||
|
..GenerationPreferences::default()
|
||||||
|
};
|
||||||
|
assert_eq!(long_context.supported_reasoning_modes(), &REASONING_MODES);
|
||||||
|
assert_eq!(long_context.effective_reasoning_mode(), ReasoningMode::Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reasoning_selection_reaches_every_model_turn() {
|
||||||
|
let cache = KvCachePreferences::default().settings();
|
||||||
|
for model in crate::model::MODEL_CHOICES {
|
||||||
|
for mode in [ReasoningMode::Direct, ReasoningMode::High] {
|
||||||
|
let generation = GenerationPreferences {
|
||||||
|
reasoning_mode: mode,
|
||||||
|
..GenerationPreferences::default()
|
||||||
|
};
|
||||||
|
assert_eq!(generation.turn_settings(model, cache).reasoning_mode, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
let generation = GenerationPreferences {
|
||||||
|
context_tokens: THINK_MAX_MIN_CONTEXT,
|
||||||
|
reasoning_mode: ReasoningMode::Max,
|
||||||
|
..GenerationPreferences::default()
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
generation.turn_settings(model, cache).reasoning_mode,
|
||||||
|
ReasoningMode::Max
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user