Skip to content

Commit 6086d79

Browse files
committed
feat(chat): Implement /compact help and simplify token warning system
- Add proper help text for '/compact help' command similar to '/context help' - Replace multiple warning levels with a single warning at 500K characters - Change token warning language to be more subtle about limits - Add '--summary' flag to show the summary after compacting - Refactor token calculation to focus on character count instead of percentages
1 parent 418ae50 commit 6086d79

File tree

4 files changed

+152
-95
lines changed

4 files changed

+152
-95
lines changed

crates/q_cli/src/cli/chat/chat_state.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,14 @@ use fig_api_client::model::ChatMessage;
66
pub enum TokenWarningLevel {
77
/// No warning, conversation is within normal limits
88
None,
9-
/// Warning level - approaching limit (60% of max)
10-
Warning,
11-
/// Critical level - near limit (90% of max)
9+
/// Critical level - at single warning threshold (500K characters)
1210
Critical,
1311
}
1412

1513
/// Constants for token usage warnings
1614
pub const CHARS_PER_TOKEN: f64 = 3.5;
1715
pub const MAX_TOKENS: usize = 200000; // Reduced for testing (original: 200_000)
18-
pub const WARNING_THRESHOLD: f64 = 0.60; // 60% of max tokens
19-
pub const CRITICAL_THRESHOLD: f64 = 0.90; // 90% of max tokens
16+
pub const MAX_CHARS: usize = 500000; // Single character-based warning threshold
2017

2118
/// Flag for the compact command - we will always clear but store the summary
2219
#[derive(Debug, Clone)]
@@ -32,20 +29,24 @@ pub struct SummarizationState {
3229
pub original_history: Option<VecDeque<ChatMessage>>,
3330
/// Optional custom prompt used for summarization
3431
pub custom_prompt: Option<String>,
32+
/// Whether to show the summary after compacting
33+
pub show_summary: bool,
3534
}
3635

3736
impl SummarizationState {
3837
pub fn new() -> Self {
3938
Self {
4039
original_history: None,
4140
custom_prompt: None,
41+
show_summary: false,
4242
}
4343
}
4444

4545
pub fn with_prompt(prompt: Option<String>) -> Self {
4646
Self {
4747
original_history: None,
4848
custom_prompt: prompt,
49+
show_summary: false,
4950
}
5051
}
5152
}

crates/q_cli/src/cli/chat/command.rs

+51-11
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ pub enum Command {
1818
Profile { subcommand: ProfileSubcommand },
1919
Context { subcommand: ContextSubcommand },
2020
PromptEditor { initial_text: Option<String> },
21-
Compact { prompt: Option<String> },
22-
Summary,
21+
Compact { prompt: Option<String>, show_summary: bool, help: bool },
2322
Tools { subcommand: Option<ToolsSubcommand> },
2423
}
2524

@@ -223,18 +222,45 @@ impl Command {
223222
return Ok(match parts[0].to_lowercase().as_str() {
224223
"clear" => Self::Clear,
225224
"help" => Self::Help,
226-
"summary" => Self::Summary,
227225
"compact" => {
228-
if parts.len() > 1 {
229-
// If there are additional arguments, join them as the prompt
230-
Self::Compact {
231-
prompt: Some(parts[1..].join(" ")),
232-
}
226+
let mut prompt = None;
227+
let mut show_summary = false;
228+
let mut help = false;
229+
230+
// Check if "help" is the first subcommand
231+
if parts.len() > 1 && parts[1].to_lowercase() == "help" {
232+
help = true;
233233
} else {
234-
// No additional arguments, use default prompt
235-
Self::Compact {
236-
prompt: None,
234+
let mut remaining_parts = Vec::new();
235+
236+
// Parse the parts to handle both prompt and flags
237+
for part in &parts[1..] {
238+
if *part == "--summary" {
239+
show_summary = true;
240+
} else {
241+
remaining_parts.push(*part);
242+
}
243+
}
244+
245+
// Check if the last word is "--summary" (which would have been captured as part of the prompt)
246+
if !remaining_parts.is_empty() {
247+
let last_idx = remaining_parts.len() - 1;
248+
if remaining_parts[last_idx] == "--summary" {
249+
remaining_parts.pop();
250+
show_summary = true;
251+
}
237252
}
253+
254+
// If we have remaining parts after parsing flags, join them as the prompt
255+
if !remaining_parts.is_empty() {
256+
prompt = Some(remaining_parts.join(" "));
257+
}
258+
}
259+
260+
Self::Compact {
261+
prompt,
262+
show_summary,
263+
help,
238264
}
239265
},
240266
"acceptall" => {
@@ -533,7 +559,21 @@ mod tests {
533559
}
534560
};
535561
}
562+
macro_rules! compact {
563+
($prompt:expr, $show_summary:expr) => {
564+
Command::Compact {
565+
prompt: $prompt,
566+
show_summary: $show_summary,
567+
help: false,
568+
}
569+
};
570+
}
536571
let tests = &[
572+
("/compact", compact!(None, false)),
573+
("/compact --summary", compact!(None, true)),
574+
("/compact custom prompt", compact!(Some("custom prompt".to_string()), false)),
575+
("/compact --summary custom prompt", compact!(Some("custom prompt".to_string()), true)),
576+
("/compact custom prompt --summary", compact!(Some("custom prompt".to_string()), true)),
537577
("/profile list", profile!(ProfileSubcommand::List)),
538578
(
539579
"/profile create new_profile",

crates/q_cli/src/cli/chat/conversation_state.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use tracing::{
3232
warn,
3333
};
3434

35-
use super::chat_state::{CHARS_PER_TOKEN, CRITICAL_THRESHOLD, MAX_TOKENS, TokenWarningLevel, WARNING_THRESHOLD};
35+
use super::chat_state::{CHARS_PER_TOKEN, MAX_CHARS, MAX_TOKENS, TokenWarningLevel};
3636
use super::context::ContextManager;
3737
use super::tools::{
3838
QueuedTool,
@@ -502,7 +502,7 @@ impl ConversationState {
502502
self.context_message_length
503503
}
504504

505-
/// Calculate the approximate token usage based on character count
505+
/// Calculate the character count and token usage
506506
pub fn calculate_token_usage(&self) -> (usize, f64) {
507507
// Calculate total character count in all messages
508508
let mut total_chars = 0;
@@ -549,21 +549,19 @@ impl ConversationState {
549549
total_chars += summary.len();
550550
}
551551

552-
// Calculate estimated tokens
552+
// Calculate estimated tokens based on character count
553553
let estimated_tokens = (total_chars as f64 / CHARS_PER_TOKEN).ceil() as usize;
554554
let usage_percentage = estimated_tokens as f64 / MAX_TOKENS as f64;
555555

556-
(estimated_tokens, usage_percentage)
556+
(total_chars, usage_percentage)
557557
}
558558

559559
/// Get the current token warning level
560560
pub fn get_token_warning_level(&self) -> TokenWarningLevel {
561-
let (_, usage_percentage) = self.calculate_token_usage();
561+
let (total_chars, _) = self.calculate_token_usage();
562562

563-
if usage_percentage >= CRITICAL_THRESHOLD {
563+
if total_chars >= MAX_CHARS {
564564
TokenWarningLevel::Critical
565-
} else if usage_percentage >= WARNING_THRESHOLD {
566-
TokenWarningLevel::Warning
567565
} else {
568566
TokenWarningLevel::None
569567
}

0 commit comments

Comments
 (0)