fix(tui) turn timing incremental (#9599)
## Summary When we send multiple assistant messages, reset the timer so "Worked for 2m 36s" is the time since the last time we showed the message, rather than an ever-increasing number. We could instead change the copy so it's more clearly a running counter. ## Testing - [x] ran locally <img width="903" height="732" alt="Screenshot 2026-01-21 at 1 42 51 AM" src="https://github.com/user-attachments/assets/bb4d827b-3a0e-48ba-bd6a-d8cd65d8e892" />
This commit is contained in:
parent
5dad1b956e
commit
f1240ff4fe
4 changed files with 62 additions and 2 deletions
|
|
@ -488,6 +488,11 @@ pub(crate) struct ChatWidget {
|
|||
// This gates rendering of the "Worked for …" separator so purely conversational turns don't
|
||||
// show an empty divider. It is reset when the separator is emitted.
|
||||
had_work_activity: bool,
|
||||
// Status-indicator elapsed seconds captured at the last emitted final-message separator.
|
||||
//
|
||||
// This lets the separator show per-chunk work time (since the previous separator) rather than
|
||||
// the total task-running time reported by the status indicator.
|
||||
last_separator_elapsed_secs: Option<u64>,
|
||||
|
||||
last_rendered_width: std::cell::Cell<Option<usize>>,
|
||||
// Feedback sink for /feedback
|
||||
|
|
@ -1542,7 +1547,8 @@ impl ChatWidget {
|
|||
let elapsed_seconds = self
|
||||
.bottom_pane
|
||||
.status_widget()
|
||||
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds);
|
||||
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds)
|
||||
.map(|current| self.worked_elapsed_from(current));
|
||||
self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds));
|
||||
self.needs_final_message_separator = false;
|
||||
self.had_work_activity = false;
|
||||
|
|
@ -1562,6 +1568,17 @@ impl ChatWidget {
|
|||
self.request_redraw();
|
||||
}
|
||||
|
||||
fn worked_elapsed_from(&mut self, current_elapsed: u64) -> u64 {
|
||||
let baseline = match self.last_separator_elapsed_secs {
|
||||
Some(last) if current_elapsed < last => 0,
|
||||
Some(last) => last,
|
||||
None => 0,
|
||||
};
|
||||
let elapsed = current_elapsed.saturating_sub(baseline);
|
||||
self.last_separator_elapsed_secs = Some(current_elapsed);
|
||||
elapsed
|
||||
}
|
||||
|
||||
pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {
|
||||
let running = self.running_commands.remove(&ev.call_id);
|
||||
if self.suppressed_exec_calls.remove(&ev.call_id) {
|
||||
|
|
@ -1906,6 +1923,7 @@ impl ChatWidget {
|
|||
pre_review_token_info: None,
|
||||
needs_final_message_separator: false,
|
||||
had_work_activity: false,
|
||||
last_separator_elapsed_secs: None,
|
||||
last_rendered_width: std::cell::Cell::new(None),
|
||||
feedback,
|
||||
current_rollout_path: None,
|
||||
|
|
@ -2022,6 +2040,7 @@ impl ChatWidget {
|
|||
pre_review_token_info: None,
|
||||
needs_final_message_separator: false,
|
||||
had_work_activity: false,
|
||||
last_separator_elapsed_secs: None,
|
||||
last_rendered_width: std::cell::Cell::new(None),
|
||||
feedback,
|
||||
current_rollout_path: None,
|
||||
|
|
|
|||
|
|
@ -838,6 +838,7 @@ async fn make_chatwidget_manual(
|
|||
pre_review_token_info: None,
|
||||
needs_final_message_separator: false,
|
||||
had_work_activity: false,
|
||||
last_separator_elapsed_secs: None,
|
||||
last_rendered_width: std::cell::Cell::new(None),
|
||||
feedback: codex_feedback::CodexFeedback::new(),
|
||||
current_rollout_path: None,
|
||||
|
|
@ -868,6 +869,16 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) {
|
|||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worked_elapsed_from_resets_when_timer_restarts() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
assert_eq!(chat.worked_elapsed_from(5), 5);
|
||||
assert_eq!(chat.worked_elapsed_from(9), 4);
|
||||
// Simulate status timer resetting (e.g., status indicator recreated for a new task).
|
||||
assert_eq!(chat.worked_elapsed_from(3), 3);
|
||||
assert_eq!(chat.worked_elapsed_from(7), 4);
|
||||
}
|
||||
|
||||
pub(crate) async fn make_chatwidget_manual_with_sender() -> (
|
||||
ChatWidget,
|
||||
AppEventSender,
|
||||
|
|
|
|||
|
|
@ -433,6 +433,11 @@ pub(crate) struct ChatWidget {
|
|||
// This gates rendering of the "Worked for …" separator so purely conversational turns don't
|
||||
// show an empty divider. It is reset when the separator is emitted.
|
||||
had_work_activity: bool,
|
||||
// Status-indicator elapsed seconds captured at the last emitted final-message separator.
|
||||
//
|
||||
// This lets the separator show per-chunk work time (since the previous separator) rather than
|
||||
// the total task-running time reported by the status indicator.
|
||||
last_separator_elapsed_secs: Option<u64>,
|
||||
|
||||
last_rendered_width: std::cell::Cell<Option<usize>>,
|
||||
// Feedback sink for /feedback
|
||||
|
|
@ -1333,7 +1338,8 @@ impl ChatWidget {
|
|||
let elapsed_seconds = self
|
||||
.bottom_pane
|
||||
.status_widget()
|
||||
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds);
|
||||
.map(super::status_indicator_widget::StatusIndicatorWidget::elapsed_seconds)
|
||||
.map(|current| self.worked_elapsed_from(current));
|
||||
self.add_to_history(history_cell::FinalMessageSeparator::new(elapsed_seconds));
|
||||
self.needs_final_message_separator = false;
|
||||
self.had_work_activity = false;
|
||||
|
|
@ -1356,6 +1362,17 @@ impl ChatWidget {
|
|||
}
|
||||
}
|
||||
|
||||
fn worked_elapsed_from(&mut self, current_elapsed: u64) -> u64 {
|
||||
let baseline = match self.last_separator_elapsed_secs {
|
||||
Some(last) if current_elapsed < last => 0,
|
||||
Some(last) => last,
|
||||
None => 0,
|
||||
};
|
||||
let elapsed = current_elapsed.saturating_sub(baseline);
|
||||
self.last_separator_elapsed_secs = Some(current_elapsed);
|
||||
elapsed
|
||||
}
|
||||
|
||||
pub(crate) fn handle_exec_end_now(&mut self, ev: ExecCommandEndEvent) {
|
||||
let running = self.running_commands.remove(&ev.call_id);
|
||||
if self.suppressed_exec_calls.remove(&ev.call_id) {
|
||||
|
|
@ -1691,6 +1708,7 @@ impl ChatWidget {
|
|||
pre_review_token_info: None,
|
||||
needs_final_message_separator: false,
|
||||
had_work_activity: false,
|
||||
last_separator_elapsed_secs: None,
|
||||
last_rendered_width: std::cell::Cell::new(None),
|
||||
feedback,
|
||||
current_rollout_path: None,
|
||||
|
|
@ -1805,6 +1823,7 @@ impl ChatWidget {
|
|||
pre_review_token_info: None,
|
||||
needs_final_message_separator: false,
|
||||
had_work_activity: false,
|
||||
last_separator_elapsed_secs: None,
|
||||
last_rendered_width: std::cell::Cell::new(None),
|
||||
feedback,
|
||||
current_rollout_path: None,
|
||||
|
|
|
|||
|
|
@ -826,6 +826,7 @@ async fn make_chatwidget_manual(
|
|||
pre_review_token_info: None,
|
||||
needs_final_message_separator: false,
|
||||
had_work_activity: false,
|
||||
last_separator_elapsed_secs: None,
|
||||
last_rendered_width: std::cell::Cell::new(None),
|
||||
feedback: codex_feedback::CodexFeedback::new(),
|
||||
current_rollout_path: None,
|
||||
|
|
@ -855,6 +856,16 @@ fn set_chatgpt_auth(chat: &mut ChatWidget) {
|
|||
));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn worked_elapsed_from_resets_when_timer_restarts() {
|
||||
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
|
||||
assert_eq!(chat.worked_elapsed_from(5), 5);
|
||||
assert_eq!(chat.worked_elapsed_from(9), 4);
|
||||
// Simulate status timer resetting (e.g., status indicator recreated for a new task).
|
||||
assert_eq!(chat.worked_elapsed_from(3), 3);
|
||||
assert_eq!(chat.worked_elapsed_from(7), 4);
|
||||
}
|
||||
|
||||
pub(crate) async fn make_chatwidget_manual_with_sender() -> (
|
||||
ChatWidget,
|
||||
AppEventSender,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue