mirror of
https://github.com/openai/codex.git
synced 2026-10-27 18:48:37 +00:00
Represent user message presentation with an enum (#57291)
## What changed Replace `UserHistoryCell`'s `spoken` boolean with `UserMessagePresentation`, defaulting to `Plain` and using `Spoken` for speech transcripts. Route rendering checks through `is_spoken()`, preserving the existing prompt styling and whitespace handling. GitOrigin-RevId: d0683a2b536a7d71f68fd9a089e6839d0ab0cc65
This commit is contained in:
@@ -55,7 +55,7 @@ fn overlapping_history_keeps_live_turn_state_and_newer_items() {
|
||||
|
||||
fn user_cell(message: &str) -> Arc<dyn HistoryCell> {
|
||||
Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: message.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
|
||||
@@ -25,7 +25,7 @@ use ratatui::buffer::Buffer;
|
||||
|
||||
pub(in crate::app) fn user_cell(message: &str) -> Arc<dyn HistoryCell> {
|
||||
Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: message.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
|
||||
@@ -59,7 +59,7 @@ fn turn(status: TurnStatus) -> Turn {
|
||||
fn user_history_cell(message: &str) -> Arc<dyn HistoryCell> {
|
||||
Arc::new(UserHistoryCell {
|
||||
message: message.to_string(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
|
||||
@@ -6226,7 +6226,7 @@ async fn render_clear_ui_header_after_long_transcript_for_snapshot() -> String {
|
||||
|
||||
let user_cell = |text: &str| -> Arc<dyn HistoryCell> {
|
||||
Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: text.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -7953,7 +7953,7 @@ async fn backtrack_selection_preserves_selected_prompt_and_requests_branch() {
|
||||
remote_image_urls: Vec<String>|
|
||||
-> Arc<dyn HistoryCell> {
|
||||
Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: text.to_string(),
|
||||
text_elements,
|
||||
local_image_paths,
|
||||
@@ -10164,7 +10164,7 @@ async fn clear_only_ui_reset_preserves_chat_session_state() {
|
||||
app.chat_widget
|
||||
.apply_external_edit("draft prompt".to_string());
|
||||
app.transcript_cells = vec![Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "old message".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
|
||||
@@ -40,7 +40,7 @@ async fn prepare_eligible_recap(app: &mut App, thread_id: ThreadId) {
|
||||
app.transcript_cells
|
||||
.push(Arc::new(crate::history_cell::UserHistoryCell {
|
||||
message: "Finish the recap implementation".to_string(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
@@ -130,7 +130,7 @@ stream_max_retries = 0
|
||||
app.transcript_cells
|
||||
.push(Arc::new(crate::history_cell::UserHistoryCell {
|
||||
message: "Keep follow-up work queued.".to_string(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
@@ -236,7 +236,7 @@ async fn manual_recap_works_when_auto_recap_disabled() -> Result<()> {
|
||||
app.transcript_cells
|
||||
.push(Arc::new(crate::history_cell::UserHistoryCell {
|
||||
message: "Summarize this conversation".to_string(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
@@ -455,7 +455,7 @@ async fn recap_generation_uses_remote_workspace_cwd() -> Result<()> {
|
||||
app.transcript_cells
|
||||
.push(Arc::new(crate::history_cell::UserHistoryCell {
|
||||
message: "Finish the recap implementation".to_string(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
|
||||
@@ -63,7 +63,7 @@ async fn deprecation_delivery_deduplicates_retained_transcript() -> Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
}),
|
||||
);
|
||||
app.open_transcript_overlay(&mut tui);
|
||||
@@ -148,7 +148,7 @@ async fn startup_warnings_preserve_stream_repair_and_backtrack_selection() -> Re
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
})];
|
||||
app.transcript_reflow.mark_resize_requested_during_stream();
|
||||
insert_warnings(&mut app, &mut tui, &["First warning"]);
|
||||
|
||||
@@ -237,7 +237,7 @@ async fn transcript_flag_off_preserves_viewer_and_backtracking() -> Result<()> {
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
}) as Arc<dyn HistoryCell>
|
||||
})
|
||||
.to_vec();
|
||||
|
||||
@@ -18,7 +18,7 @@ async fn text_selection_temporarily_replaces_the_rendered_backtrack_highlight()
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
remote_image_urls: Vec::new(),
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
})];
|
||||
app.open_transcript_overlay(&mut tui);
|
||||
while events.try_recv().is_ok() {}
|
||||
|
||||
@@ -22,7 +22,7 @@ fn markdown_transcript_preserves_messages_and_formats_activity() {
|
||||
assert!(render_markdown_transcript(&[]).is_err());
|
||||
let user = |message: &str, local_image_paths| {
|
||||
Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: message.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths,
|
||||
|
||||
@@ -1215,7 +1215,7 @@ mod tests {
|
||||
assert!(!has_backtrack_target(&cells));
|
||||
|
||||
cells.push(Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
|
||||
@@ -18,7 +18,14 @@ pub(crate) struct UserHistoryCell {
|
||||
pub text_elements: Vec<TextElement>,
|
||||
pub local_image_paths: Vec<PathBuf>,
|
||||
pub remote_image_urls: Vec<String>,
|
||||
pub(crate) spoken: bool,
|
||||
pub(crate) presentation: UserMessagePresentation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) enum UserMessagePresentation {
|
||||
#[default]
|
||||
Plain,
|
||||
Spoken,
|
||||
}
|
||||
|
||||
/// Remove CSI sequences and control characters, preserving tabs and newlines.
|
||||
@@ -152,6 +159,10 @@ fn build_user_message_lines_with_elements(
|
||||
}
|
||||
|
||||
impl UserHistoryCell {
|
||||
pub(crate) fn is_spoken(&self) -> bool {
|
||||
matches!(self.presentation, UserMessagePresentation::Spoken)
|
||||
}
|
||||
|
||||
pub(crate) fn has_visible_content(&self) -> bool {
|
||||
!sanitize_user_text(Cow::Borrowed(&self.message))
|
||||
.trim()
|
||||
@@ -183,7 +194,7 @@ impl HistoryCell for UserHistoryCell {
|
||||
fn display_hyperlink_lines(&self, width: u16) -> Vec<HyperlinkLine> {
|
||||
let sanitized_message = sanitize_user_text((&self.message).into());
|
||||
// Speech transcripts can begin with whitespace; the marker already supplies its separator.
|
||||
let message = if self.spoken {
|
||||
let message = if self.is_spoken() {
|
||||
sanitized_message.trim_start()
|
||||
} else {
|
||||
sanitized_message.as_ref()
|
||||
@@ -231,7 +242,7 @@ impl HistoryCell for UserHistoryCell {
|
||||
if let Some(wrapped_message) = wrapped_message {
|
||||
lines.extend(prefix_hyperlink_lines(
|
||||
wrapped_message,
|
||||
if self.spoken {
|
||||
if self.is_spoken() {
|
||||
"› ".red().bold()
|
||||
} else {
|
||||
"› ".bold().dim()
|
||||
@@ -730,13 +741,13 @@ pub(crate) fn new_user_prompt(
|
||||
text_elements,
|
||||
local_image_paths,
|
||||
remote_image_urls,
|
||||
spoken: false,
|
||||
presentation: UserMessagePresentation::Plain,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_spoken_user_prompt(message: String) -> UserHistoryCell {
|
||||
let mut cell = new_user_prompt(message, Vec::new(), Vec::new(), Vec::new());
|
||||
cell.spoken = true;
|
||||
cell.presentation = UserMessagePresentation::Spoken;
|
||||
cell
|
||||
}
|
||||
|
||||
|
||||
@@ -2311,7 +2311,7 @@ fn ran_cell_multiline_with_stderr_snapshot() {
|
||||
fn user_history_cell_wraps_and_prefixes_each_line_snapshot() {
|
||||
let msg = "_count_r\x1b[13;2:3uows";
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: msg.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -2335,7 +2335,7 @@ fn user_history_cell_wraps_long_urls_inside_the_message_gutter() {
|
||||
);
|
||||
let image_start = message.find("[Image #1]").unwrap();
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message,
|
||||
text_elements: vec![TextElement::new(
|
||||
(image_start..image_start + "[Image #1]".len()).into(),
|
||||
@@ -2386,7 +2386,7 @@ fn user_history_cell_wraps_long_urls_inside_the_message_gutter() {
|
||||
#[test]
|
||||
fn user_history_cell_renders_remote_image_urls() {
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "describe these".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -2407,7 +2407,7 @@ fn user_image_labels_follow_the_painted_prompt_surface() {
|
||||
|
||||
let placeholder = "[Image #1]";
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: format!("{placeholder} describe these"),
|
||||
text_elements: vec![TextElement::new(
|
||||
(0..placeholder.len()).into(),
|
||||
@@ -2443,7 +2443,7 @@ fn user_image_labels_follow_the_painted_prompt_surface() {
|
||||
#[test]
|
||||
fn user_history_cell_summarizes_inline_data_urls() {
|
||||
let mut cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "describe inline image".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -2469,7 +2469,7 @@ fn user_history_cell_summarizes_inline_data_urls() {
|
||||
#[test]
|
||||
fn user_history_cell_numbers_multiple_remote_images() {
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "describe both".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -2489,7 +2489,7 @@ fn user_history_cell_numbers_multiple_remote_images() {
|
||||
#[test]
|
||||
fn user_history_cell_height_matches_rendered_lines_with_remote_images() {
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "line one\nline two".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -2512,7 +2512,7 @@ fn user_history_cell_height_matches_rendered_lines_with_remote_images() {
|
||||
#[test]
|
||||
fn user_history_cell_trims_trailing_blank_message_lines() {
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "line one\n\n \n\t \n".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -2533,7 +2533,7 @@ fn user_history_cell_trims_trailing_blank_message_lines() {
|
||||
fn user_history_cell_trims_trailing_blank_message_lines_with_text_elements() {
|
||||
let message = "tokenized\n\n\n".to_string();
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message,
|
||||
text_elements: vec![TextElement::new(
|
||||
(0..8).into(),
|
||||
@@ -2557,7 +2557,7 @@ fn user_history_cell_trims_trailing_blank_message_lines_with_text_elements() {
|
||||
fn render_uses_wrapping_for_long_url_like_line() {
|
||||
let url = "https://example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890/artifacts/reports/performance/summary/detail/with/a/very/long/path/that/keeps/going/for/testing/purposes-only-and-does/not/need/to/resolve/index.html?session_id=abc123def456ghi789jkl012mno345pqr678stu901vwx234yz";
|
||||
let cell: Box<dyn HistoryCell> = Box::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: url.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -3049,7 +3049,7 @@ fn agent_markdown_cell_narrow_width_shows_prefix_only() {
|
||||
#[test]
|
||||
fn wrapped_and_prefixed_cells_handle_tiny_widths() {
|
||||
let user_cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "tiny width coverage for wrapped user history".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
@@ -3156,7 +3156,7 @@ fn consolidation_walker_replaces_agent_message_cells() {
|
||||
|
||||
// Build a transcript with: [UserCell, AgentMsg(head), AgentMsg(cont), AgentMsg(cont)]
|
||||
let user = Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: "hello".to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
|
||||
@@ -834,7 +834,7 @@ mod tests {
|
||||
|
||||
let url = "https://example.test/forwarded/threads/10930?page=1&queue=customer_support_unprocessed&forwardedScope=all";
|
||||
let cell = UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: url.to_string(),
|
||||
text_elements: Vec::new(),
|
||||
local_image_paths: Vec::new(),
|
||||
|
||||
@@ -259,7 +259,7 @@ fn item_to_cells(
|
||||
item.text_elements()
|
||||
};
|
||||
cells.push(Arc::new(UserHistoryCell {
|
||||
spoken: false,
|
||||
presentation: Default::default(),
|
||||
message: reply_text.unwrap_or(message),
|
||||
text_elements,
|
||||
local_image_paths: item.local_image_paths(),
|
||||
|
||||
@@ -53,7 +53,7 @@ pub(super) fn line(
|
||||
Some((
|
||||
truncate_line_with_ellipsis_if_overflow(
|
||||
Line::from(vec![
|
||||
if prompt.spoken {
|
||||
if prompt.is_spoken() {
|
||||
"› ".red().bold()
|
||||
} else {
|
||||
"› ".bold().dim()
|
||||
|
||||
Reference in New Issue
Block a user