返回 CodeWhale
effort.rs
根目录 / crates / tui / src / commands / groups / core / effort.rs
1 //! Effort / Thinking slash commands — `/effort` and `/thinking`.
2 //!
3 //! Lets users change the model's reasoning effort via slash command as well as `Ctrl+T`.
4 //! Mirrors the Muse Spark UX where `/effort [<none|minimal|low|medium|high|xhigh|ultra>]`
5 //! is offered via autocomplete. Both `/effort` and `/thinking` are aliases for the same
6 //! underlying `ReasoningEffort` state, keeping `Thinking` as the canonical UI label
7 //! (per user request to keep it as Thinking and fix for everything).
8
9 use crate::commands::{CommandResult, traits::CommandInfo};
10 use crate::reasoning_preference::ReasoningEffort;
11 use crate::tui::app::App;
12 use codewhale_localization::{MessageId, tr};
13
14 pub const EFFORT_INFO: CommandInfo = CommandInfo {
15 name: "effort",
16 aliases: &["thinking", "reasoning", "reason"],
17 usage: "/effort [<none|minimal|low|medium|high|xhigh|ultra|max|auto|off>]",
18 description_id: MessageId::CmdEffortDescription,
19 };
20
21 pub fn effort(app: &mut App, args: Option<&str>) -> CommandResult {
22 let arg = args.unwrap_or("").trim().to_ascii_lowercase();
23 // Determine available efforts for the current model (like model picker does)
24 let available: Vec<ReasoningEffort> = {
25 let provider = app.api_provider;
26 let base_url = app.active_route_base_url.clone();
27 // Try to get the current wire model; fallback to app.model
28 let wire_model = crate::tui::model_picker::picker_efforts_for_route(
29 provider,
30 &base_url,
31 &app.model,
32 app.auto_model,
33 );
34 // If picker returns empty, fallback to the model's catalog efforts
35 if !wire_model.is_empty() {
36 wire_model
37 } else {
38 vec![]
39 }
40 };
41 let available_labels: Vec<String> = if available.is_empty() {
42 vec![
43 "none".into(),
44 "minimal".into(),
45 "low".into(),
46 "medium".into(),
47 "high".into(),
48 "xhigh".into(),
49 "ultra".into(),
50 "max".into(),
51 "auto".into(),
52 "off".into(),
53 ]
54 } else {
55 available
56 .iter()
57 .map(|e| e.display_label_for_provider(app.api_provider).to_string())
58 .collect()
59 };
60 if arg.is_empty() {
61 // No arg: same path as Ctrl+T / the hotbar so cycle, persist, and
62 // receipts cannot drift by command surface.
63 let prev = app.reasoning_effort;
64 let _ = app.cycle_effort();
65 return CommandResult::message(format!(
66 "Effort: {} → {} (available: {})",
67 prev.display_label_for_provider(app.api_provider),
68 app.reasoning_effort
69 .display_label_for_provider(app.api_provider),
70 available_labels.join("|")
71 ));
72 }
73 let Ok(effort) = ReasoningEffort::parse_strict(&arg) else {
74 let hint = format!(
75 "Usage: /effort <{}> (also /thinking)",
76 available_labels.join("|")
77 );
78 return CommandResult::error(format!(
79 "{} — {}",
80 tr(app.ui_locale, MessageId::CmdEffortDescription),
81 hint
82 ));
83 };
84 let requested = if app.auto_model {
85 effort
86 } else {
87 effort.normalize_for_route(app.api_provider, &app.active_route_base_url, &app.model)
88 };
89 if !available.is_empty() && !available.contains(&requested) && !available.contains(&effort) {
90 return CommandResult::error(format!(
91 "'{}' not available for {} — available: {}",
92 arg,
93 app.model,
94 available_labels.join("|")
95 ));
96 }
97 app.commit_reasoning_effort(requested);
98 CommandResult::message(format!(
99 "Effort set to {}",
100 requested.display_label_for_provider(app.api_provider)
101 ))
102 }
103
103 lines RUST