mirror of
https://github.com/openai/codex.git
synced 2026-11-12 06:40:35 +00:00
Fix TUI status visibility around streamed output (#33105)
## What changed - Hide the working-status indicator when finalized assistant output is committed to the transcript, including single-line final answers. - Restore the indicator when image generation begins during a running task after its streamed preamble has been flushed. ## Testing - Add snapshot coverage for a single-line final answer and for image generation following a single-line preamble. GitOrigin-RevId: e294a11cda3f0ce46f2bbed1be2a1380c9c73085
This commit is contained in:
1 parent
effd58d750
commit
fca8c00f11
6 files changed
+96
No files matched your search
+11
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
source: tui/src/chatwidget/tests/exec_flow.rs
|
||||||
|
expression: normalized_backend_snapshot(terminal.backend())
|
||||||
|
---
|
||||||
|
" "
|
||||||
|
"• Working (0s • esc to interrupt) "
|
||||||
|
" "
|
||||||
|
" "
|
||||||
|
"› Ask Codex to do anything "
|
||||||
|
" "
|
||||||
|
" gpt-5.6-sol default · /tmp/project "
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
source: tui/src/chatwidget/tests/status_and_layout.rs
|
||||||
|
expression: normalize_snapshot_paths(terminal.backend().vt100().screen().contents())
|
||||||
|
---
|
||||||
|
|
||||||
|
› count to 1
|
||||||
|
|
||||||
|
|
||||||
|
• 1
|
||||||
|
|
||||||
|
|
||||||
|
› Ask Codex to do anything
|
||||||
|
|
||||||
|
gpt-5 default · /tmp/project
|
||||||
@@ -26,6 +26,11 @@ impl ChatWidget {
|
|||||||
};
|
};
|
||||||
self.clear_active_stream_tail();
|
self.clear_active_stream_tail();
|
||||||
let (cell, source) = controller.finalize();
|
let (cell, source) = controller.finalize();
|
||||||
|
// Match newline-committed streaming behavior: once assistant output is ready to be
|
||||||
|
// committed into history, hide the inline status row so transcript content replaces it.
|
||||||
|
if cell.is_some() {
|
||||||
|
self.bottom_pane.hide_status_indicator();
|
||||||
|
}
|
||||||
let deferred_history_cell =
|
let deferred_history_cell =
|
||||||
if scrollback_reflow == crate::app_event::ConsolidationScrollbackReflow::Required {
|
if scrollback_reflow == crate::app_event::ConsolidationScrollbackReflow::Required {
|
||||||
cell
|
cell
|
||||||
|
|||||||
@@ -877,6 +877,30 @@ async fn view_image_tool_call_preserves_foreign_path() {
|
|||||||
assert_chatwidget_snapshot!("foreign_image_attachment_history_snapshot", combined);
|
assert_chatwidget_snapshot!("foreign_image_attachment_history_snapshot", combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn image_generation_begin_restores_working_status_after_single_line_preamble() {
|
||||||
|
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||||
|
|
||||||
|
chat.on_task_started();
|
||||||
|
chat.on_agent_message_delta("Generating an image.".to_string());
|
||||||
|
chat.on_image_generation_begin();
|
||||||
|
|
||||||
|
assert!(chat.bottom_pane.is_task_running());
|
||||||
|
assert!(chat.bottom_pane.status_indicator_visible());
|
||||||
|
|
||||||
|
let width: u16 = 80;
|
||||||
|
let height = chat.desired_height(width);
|
||||||
|
let mut terminal = ratatui::Terminal::new(ratatui::backend::TestBackend::new(width, height))
|
||||||
|
.expect("create terminal");
|
||||||
|
terminal
|
||||||
|
.draw(|frame| chat.render(frame.area(), frame.buffer_mut()))
|
||||||
|
.expect("draw image generation status");
|
||||||
|
assert_chatwidget_snapshot!(
|
||||||
|
"image_generation_begin_restores_working_status",
|
||||||
|
normalized_backend_snapshot(terminal.backend())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn image_generation_call_adds_history_cell() {
|
async fn image_generation_call_adds_history_cell() {
|
||||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||||
|
|||||||
@@ -1525,6 +1525,45 @@ async fn streaming_final_answer_keeps_task_running_state() {
|
|||||||
assert!(!chat.bottom_pane.quit_shortcut_hint_visible());
|
assert!(!chat.bottom_pane.quit_shortcut_hint_visible());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_line_final_answer_hides_working_status_snapshot() {
|
||||||
|
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await;
|
||||||
|
chat.thread_id = Some(ThreadId::new());
|
||||||
|
|
||||||
|
complete_user_message(&mut chat, "user-1", "count to 1");
|
||||||
|
chat.on_task_started();
|
||||||
|
complete_assistant_message(
|
||||||
|
&mut chat,
|
||||||
|
"msg-final-single-line",
|
||||||
|
"1",
|
||||||
|
Some(MessagePhase::FinalAnswer),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(chat.bottom_pane.is_task_running());
|
||||||
|
assert!(!chat.bottom_pane.status_indicator_visible());
|
||||||
|
|
||||||
|
let width: u16 = 40;
|
||||||
|
let vt_height: u16 = 10;
|
||||||
|
let ui_height = chat.desired_height(width);
|
||||||
|
let viewport = Rect::new(0, vt_height - ui_height - 1, width, ui_height);
|
||||||
|
let backend = VT100Backend::new(width, vt_height);
|
||||||
|
let mut terminal = crate::custom_terminal::Terminal::with_options(backend).expect("terminal");
|
||||||
|
terminal.set_viewport_area(viewport);
|
||||||
|
|
||||||
|
for lines in drain_insert_history(&mut rx) {
|
||||||
|
crate::insert_history::insert_history_lines(&mut terminal, lines)
|
||||||
|
.expect("insert history lines");
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal
|
||||||
|
.draw(|frame| chat.render(frame.area(), frame.buffer_mut()))
|
||||||
|
.expect("draw final answer");
|
||||||
|
assert_chatwidget_snapshot!(
|
||||||
|
"single_line_final_answer_hides_working_status",
|
||||||
|
normalize_snapshot_paths(terminal.backend().vt100().screen().contents())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn ctrl_c_interrupt_pauses_active_goal_turn() {
|
async fn ctrl_c_interrupt_pauses_active_goal_turn() {
|
||||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ impl ChatWidget {
|
|||||||
pub(super) fn on_image_generation_begin(&mut self) {
|
pub(super) fn on_image_generation_begin(&mut self) {
|
||||||
self.record_visible_turn_activity();
|
self.record_visible_turn_activity();
|
||||||
self.flush_answer_stream_with_separator();
|
self.flush_answer_stream_with_separator();
|
||||||
|
if self.bottom_pane.is_task_running() {
|
||||||
|
self.bottom_pane.ensure_status_indicator();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn on_image_generation_end(
|
pub(super) fn on_image_generation_end(
|
||||||
|
|||||||
Reference in new issue
Block a user