mirror of
https://github.com/openai/codex.git
synced 2026-09-14 11:57:03 +00:00
tui: use lightweight pending steer compare keys
This commit is contained in:
@@ -38,6 +38,7 @@ use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::app_event::RealtimeAudioDeviceKind;
|
||||
use self::realtime::PendingSteerCompareKey;
|
||||
#[cfg(all(not(target_os = "linux"), feature = "voice-input"))]
|
||||
use crate::audio_device::list_realtime_audio_device_names;
|
||||
use crate::bottom_pane::StatusLineItem;
|
||||
@@ -760,7 +761,7 @@ impl From<&str> for UserMessage {
|
||||
|
||||
struct PendingSteer {
|
||||
user_message: UserMessage,
|
||||
compare_key: RenderedUserMessageEvent,
|
||||
compare_key: PendingSteerCompareKey,
|
||||
}
|
||||
|
||||
pub(crate) fn create_initial_user_message(
|
||||
@@ -4288,7 +4289,7 @@ impl ChatWidget {
|
||||
text_elements: text_elements.clone(),
|
||||
mention_bindings: mention_bindings.clone(),
|
||||
},
|
||||
compare_key: Self::rendered_user_message_event_from_normalized_items(&items),
|
||||
compare_key: Self::pending_steer_compare_key_from_items(&items),
|
||||
});
|
||||
let personality = self
|
||||
.config
|
||||
@@ -4656,10 +4657,11 @@ impl ChatWidget {
|
||||
unreachable!("user message item should convert to a legacy user message");
|
||||
};
|
||||
let rendered = Self::rendered_user_message_event_from_event(&event);
|
||||
let compare_key = Self::pending_steer_compare_key_from_item(item);
|
||||
let should_render = if self
|
||||
.pending_steers
|
||||
.front()
|
||||
.is_some_and(|pending| pending.compare_key == rendered)
|
||||
.is_some_and(|pending| pending.compare_key == compare_key)
|
||||
{
|
||||
self.pending_steers.pop_front();
|
||||
self.refresh_pending_input_preview();
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
use super::*;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::ResponseInputItem;
|
||||
use codex_protocol::models::is_image_close_tag_text;
|
||||
use codex_protocol::models::is_image_open_tag_text;
|
||||
use codex_protocol::models::is_local_image_close_tag_text;
|
||||
use codex_protocol::models::is_local_image_open_tag_text;
|
||||
use codex_protocol::protocol::ConversationStartParams;
|
||||
use codex_protocol::protocol::RealtimeAudioFrame;
|
||||
use codex_protocol::protocol::RealtimeConversationClosedEvent;
|
||||
@@ -61,6 +55,12 @@ pub(super) struct RenderedUserMessageEvent {
|
||||
pub(super) text_elements: Vec<TextElement>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) struct PendingSteerCompareKey {
|
||||
pub(super) message: String,
|
||||
pub(super) image_count: usize,
|
||||
}
|
||||
|
||||
impl ChatWidget {
|
||||
pub(super) fn rendered_user_message_event_from_parts(
|
||||
message: String,
|
||||
@@ -87,52 +87,48 @@ impl ChatWidget {
|
||||
)
|
||||
}
|
||||
|
||||
/// Build the compare key for a submitted pending steer using the same lossy
|
||||
/// `UserInput -> ResponseInputItem -> UserMessageItem` normalization that core
|
||||
/// applies when steering input into an active turn.
|
||||
///
|
||||
/// Pending steers keep the full `UserMessage` for interrupt restore, but they
|
||||
/// must match `ItemCompleted(UserMessage)` against the normalized shape core
|
||||
/// later emits after draining pending input.
|
||||
pub(super) fn rendered_user_message_event_from_normalized_items(
|
||||
/// Build the compare key for a submitted pending steer without invoking the
|
||||
/// expensive request-serialization path. Pending steers only need to match the
|
||||
/// committed `ItemCompleted(UserMessage)` emitted after core drains input, which
|
||||
/// preserves flattened text and total image count but not UI-only text ranges or
|
||||
/// local image paths.
|
||||
pub(super) fn pending_steer_compare_key_from_items(
|
||||
items: &[UserInput],
|
||||
) -> RenderedUserMessageEvent {
|
||||
let response_input_item = ResponseInputItem::from(items.to_vec());
|
||||
let ResponseInputItem::Message { role, content } = response_input_item else {
|
||||
unreachable!(
|
||||
"user inputs must convert to ResponseInputItem::Message, got {response_input_item:?}"
|
||||
);
|
||||
};
|
||||
debug_assert_eq!(role, "user");
|
||||
|
||||
) -> PendingSteerCompareKey {
|
||||
let mut message = String::new();
|
||||
let mut remote_image_urls = Vec::new();
|
||||
let mut image_count = 0;
|
||||
|
||||
for (idx, content_item) in content.iter().enumerate() {
|
||||
match content_item {
|
||||
ContentItem::InputText { text } => {
|
||||
if (is_local_image_open_tag_text(text) || is_image_open_tag_text(text))
|
||||
&& matches!(content.get(idx + 1), Some(ContentItem::InputImage { .. }))
|
||||
|| (idx > 0
|
||||
&& (is_local_image_close_tag_text(text)
|
||||
|| is_image_close_tag_text(text))
|
||||
&& matches!(content.get(idx - 1), Some(ContentItem::InputImage { .. })))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
message.push_str(text);
|
||||
}
|
||||
ContentItem::InputImage { image_url } => remote_image_urls.push(image_url.clone()),
|
||||
ContentItem::OutputText { .. } => {}
|
||||
for item in items {
|
||||
match item {
|
||||
UserInput::Text { text, .. } => message.push_str(text),
|
||||
UserInput::Image { .. } | UserInput::LocalImage { .. } => image_count += 1,
|
||||
UserInput::Skill { .. } | UserInput::Mention { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Self::rendered_user_message_event_from_parts(
|
||||
PendingSteerCompareKey {
|
||||
message,
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
remote_image_urls,
|
||||
)
|
||||
image_count,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn pending_steer_compare_key_from_item(
|
||||
item: &codex_protocol::items::UserMessageItem,
|
||||
) -> PendingSteerCompareKey {
|
||||
let mut image_count = 0;
|
||||
for input in &item.content {
|
||||
match input {
|
||||
UserInput::Image { .. } | UserInput::LocalImage { .. } => image_count += 1,
|
||||
UserInput::Text { .. } | UserInput::Skill { .. } | UserInput::Mention { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
PendingSteerCompareKey {
|
||||
message: item.message(),
|
||||
image_count,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -3236,12 +3236,10 @@ fn complete_assistant_message(
|
||||
fn pending_steer(text: &str) -> PendingSteer {
|
||||
PendingSteer {
|
||||
user_message: UserMessage::from(text),
|
||||
compare_key: ChatWidget::rendered_user_message_event_from_parts(
|
||||
text.to_string(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
compare_key: PendingSteerCompareKey {
|
||||
message: text.to_string(),
|
||||
image_count: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3875,7 +3873,7 @@ async fn item_completed_only_pops_front_pending_steer() {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn normalized_item_completed_pops_pending_steer_with_local_image_and_text_elements() {
|
||||
async fn item_completed_pops_pending_steer_with_local_image_and_text_elements() {
|
||||
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(None).await;
|
||||
chat.thread_id = Some(ThreadId::new());
|
||||
chat.on_task_started();
|
||||
@@ -3911,21 +3909,18 @@ async fn normalized_item_completed_pops_pending_steer_with_local_image_and_text_
|
||||
let pending = chat.pending_steers.front().unwrap();
|
||||
assert_eq!(pending.user_message.local_images.len(), 1);
|
||||
assert_eq!(pending.user_message.text_elements.len(), 1);
|
||||
assert!(pending.compare_key.local_images.is_empty());
|
||||
assert!(pending.compare_key.text_elements.is_empty());
|
||||
assert_eq!(pending.compare_key.message, text);
|
||||
assert_eq!(pending.compare_key.remote_image_urls.len(), 1);
|
||||
let compare_key = pending.compare_key.clone();
|
||||
assert_eq!(pending.compare_key.image_count, 1);
|
||||
|
||||
complete_user_message_for_inputs(
|
||||
&mut chat,
|
||||
"user-1",
|
||||
vec![
|
||||
UserInput::Image {
|
||||
image_url: compare_key.remote_image_urls[0].clone(),
|
||||
image_url: "data:image/png;base64,placeholder".to_string(),
|
||||
},
|
||||
UserInput::Text {
|
||||
text: compare_key.message,
|
||||
text,
|
||||
text_elements: Vec::new(),
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user