Skip to content

Commit 2153735

Browse files
Merge main branch and resolve conflicts
Resolved merge conflicts in streaming.rs by keeping the main branch structure while applying the improved timeout error message. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2 parents 1a16cdf + 12418cc commit 2153735

File tree

6 files changed

+220
-171
lines changed

6 files changed

+220
-171
lines changed

src/cortex-tui/src/app/state.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -460,9 +460,14 @@ impl AppState {
460460
// ============================================================================
461461

462462
impl AppState {
463-
/// Start streaming a response
464-
pub fn start_streaming(&mut self, tool: Option<String>) {
465-
self.streaming.start(tool);
463+
/// Start streaming a response.
464+
///
465+
/// # Arguments
466+
/// * `tool` - Optional tool name being executed
467+
/// * `reset_timer` - If true, resets the prompt elapsed timer (use for new user prompts).
468+
/// If false, preserves existing timer (use for tool continuations).
469+
pub fn start_streaming(&mut self, tool: Option<String>, reset_timer: bool) {
470+
self.streaming.start(tool, reset_timer);
466471
// Use typewriter only if streaming animation is enabled
467472
if self.streaming_enabled {
468473
self.typewriter = Some(Typewriter::dynamic(String::new(), 500.0));

src/cortex-tui/src/app/streaming.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,20 @@ pub struct StreamingState {
2121
}
2222

2323
impl StreamingState {
24-
pub fn start(&mut self, tool: Option<String>) {
24+
/// Start streaming with option to reset the prompt timer.
25+
///
26+
/// # Arguments
27+
/// * `tool` - Optional tool name being executed
28+
/// * `reset_timer` - If true, resets `prompt_started_at` to now (use for new user prompts).
29+
/// If false, preserves existing timer (use for tool continuations).
30+
pub fn start(&mut self, tool: Option<String>, reset_timer: bool) {
2531
self.is_streaming = true;
2632
self.thinking = true;
2733
self.current_tool = tool;
2834
self.task_started_at = Some(Instant::now());
29-
// Only set prompt_started_at if not already set (first call in a turn)
30-
if self.prompt_started_at.is_none() {
35+
// Reset prompt timer only when explicitly requested (new user prompt)
36+
// or when not yet set (first call)
37+
if reset_timer || self.prompt_started_at.is_none() {
3138
self.prompt_started_at = Some(Instant::now());
3239
}
3340
}

src/cortex-tui/src/runner/event_loop/core.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,9 @@ impl EventLoop {
446446
task.abort();
447447
}
448448

449+
// Reset ESC timer after cancellation to prevent accidental quit
450+
self.app_state.reset_esc();
451+
449452
self.stream_controller.reset();
450453
}
451454
}

src/cortex-tui/src/runner/event_loop/input.rs

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -368,24 +368,45 @@ impl EventLoop {
368368
return Ok(());
369369
}
370370

371-
// Check if app is idle (nothing active that ESC should cancel)
372-
let is_idle = !self.app_state.streaming.is_streaming
373-
&& self.app_state.pending_approval.is_none()
374-
&& !self.app_state.has_queued_messages()
375-
&& !self.app_state.autocomplete.visible;
376-
377-
if is_idle {
378-
if self.app_state.handle_esc() {
379-
self.app_state.set_quit();
380-
return Ok(());
381-
} else {
382-
self.app_state.toasts.info("Press ESC again to quit");
383-
self.render(terminal)?;
384-
return Ok(());
385-
}
371+
// Priority 2: If streaming is active, cancel it
372+
if self.app_state.streaming.is_streaming {
373+
self.cancel_streaming();
374+
self.render(terminal)?;
375+
return Ok(());
386376
}
387377

388-
Ok(())
378+
// Priority 3: If there are queued messages, cancel them
379+
if self.app_state.has_queued_messages() {
380+
let count = self.app_state.queued_count();
381+
self.app_state.clear_message_queue();
382+
self.add_system_message(&format!("Cancelled {} queued message(s)", count));
383+
self.render(terminal)?;
384+
return Ok(());
385+
}
386+
387+
// Priority 4: If there's pending approval, reject it
388+
if self.app_state.pending_approval.is_some() {
389+
self.app_state.reject();
390+
self.render(terminal)?;
391+
return Ok(());
392+
}
393+
394+
// Priority 5: If autocomplete is visible, hide it (already handled in handle_autocomplete_key)
395+
if self.app_state.autocomplete.visible {
396+
self.app_state.autocomplete.hide();
397+
self.render(terminal)?;
398+
return Ok(());
399+
}
400+
401+
// Priority 6: Double-tap ESC to quit when idle
402+
if self.app_state.handle_esc() {
403+
self.app_state.set_quit();
404+
Ok(())
405+
} else {
406+
self.app_state.toasts.info("Press ESC again to quit");
407+
self.render(terminal)?;
408+
Ok(())
409+
}
389410
}
390411

391412
/// Handle autocomplete navigation keys
@@ -596,7 +617,9 @@ impl EventLoop {
596617
match event {
597618
AppEvent::StreamingStarted => {
598619
self.stream_controller.start_processing();
599-
self.app_state.start_streaming(None);
620+
// Don't reset timer here - this is triggered by backend TaskStarted event
621+
// which could be either a new prompt or a continuation
622+
self.app_state.start_streaming(None, false);
600623
}
601624

602625
AppEvent::StreamingChunk(chunk) => {

0 commit comments

Comments
 (0)