replace /prompts with a rotating placeholder (#2314)

This commit is contained in:
Jeremy Rose
2025-08-15 22:37:10 -04:00
committed by GitHub
parent d3078b9adc
commit 7a80d3c96c
11 changed files with 84 additions and 72 deletions

2
codex-rs/Cargo.lock generated
View File

@@ -930,7 +930,7 @@ dependencies = [
"once_cell", "once_cell",
"path-clean", "path-clean",
"pretty_assertions", "pretty_assertions",
"rand 0.8.5", "rand 0.9.2",
"ratatui", "ratatui",
"ratatui-image", "ratatui-image",
"regex-lite", "regex-lite",

View File

@@ -75,6 +75,7 @@ tui-markdown = "0.3.3"
unicode-segmentation = "1.12.0" unicode-segmentation = "1.12.0"
unicode-width = "0.1" unicode-width = "0.1"
uuid = "1" uuid = "1"
rand = "0.9"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2" libc = "0.2"
@@ -84,5 +85,5 @@ libc = "0.2"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
insta = "1.43.1" insta = "1.43.1"
pretty_assertions = "1" pretty_assertions = "1"
rand = "0.8" rand = "0.9"
vt100 = "0.16.2" vt100 = "0.16.2"

View File

@@ -416,11 +416,6 @@ impl App<'_> {
widget.add_status_output(); widget.add_status_output();
} }
} }
SlashCommand::Prompts => {
if let AppState::Chat { widget } = &mut self.app_state {
widget.add_prompts_output();
}
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
SlashCommand::TestApproval => { SlashCommand::TestApproval => {
use codex_core::protocol::EventMsg; use codex_core::protocol::EventMsg;

View File

@@ -98,6 +98,7 @@ mod tests {
app_event_tx: AppEventSender::new(tx_raw2), app_event_tx: AppEventSender::new(tx_raw2),
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
assert_eq!(CancellationEvent::Handled, view.on_ctrl_c(&mut pane)); assert_eq!(CancellationEvent::Handled, view.on_ctrl_c(&mut pane));
assert!(view.queue.is_empty()); assert!(view.queue.is_empty());

View File

@@ -31,7 +31,6 @@ use crate::bottom_pane::textarea::TextAreaState;
use codex_file_search::FileMatch; use codex_file_search::FileMatch;
use std::cell::RefCell; use std::cell::RefCell;
const BASE_PLACEHOLDER_TEXT: &str = "Ask Codex to do anything";
/// If the pasted content exceeds this number of characters, replace it with a /// If the pasted content exceeds this number of characters, replace it with a
/// placeholder in the UI. /// placeholder in the UI.
const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000; const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000;
@@ -61,6 +60,7 @@ pub(crate) struct ChatComposer {
pending_pastes: Vec<(String, String)>, pending_pastes: Vec<(String, String)>,
token_usage_info: Option<TokenUsageInfo>, token_usage_info: Option<TokenUsageInfo>,
has_focus: bool, has_focus: bool,
placeholder_text: String,
} }
/// Popup state at most one can be visible at any time. /// Popup state at most one can be visible at any time.
@@ -75,6 +75,7 @@ impl ChatComposer {
has_input_focus: bool, has_input_focus: bool,
app_event_tx: AppEventSender, app_event_tx: AppEventSender,
enhanced_keys_supported: bool, enhanced_keys_supported: bool,
placeholder_text: String,
) -> Self { ) -> Self {
let use_shift_enter_hint = enhanced_keys_supported; let use_shift_enter_hint = enhanced_keys_supported;
@@ -91,6 +92,7 @@ impl ChatComposer {
pending_pastes: Vec::new(), pending_pastes: Vec::new(),
token_usage_info: None, token_usage_info: None,
has_focus: has_input_focus, has_focus: has_input_focus,
placeholder_text,
} }
} }
@@ -712,7 +714,7 @@ impl WidgetRef for &ChatComposer {
let mut state = self.textarea_state.borrow_mut(); let mut state = self.textarea_state.borrow_mut();
StatefulWidgetRef::render_ref(&(&self.textarea), textarea_rect, buf, &mut state); StatefulWidgetRef::render_ref(&(&self.textarea), textarea_rect, buf, &mut state);
if self.textarea.text().is_empty() { if self.textarea.text().is_empty() {
Line::from(BASE_PLACEHOLDER_TEXT) Line::from(self.placeholder_text.as_str())
.style(Style::default().dim()) .style(Style::default().dim())
.render_ref(textarea_rect.inner(Margin::new(1, 0)), buf); .render_ref(textarea_rect.inner(Margin::new(1, 0)), buf);
} }
@@ -885,7 +887,8 @@ mod tests {
let (tx, _rx) = std::sync::mpsc::channel(); let (tx, _rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
let needs_redraw = composer.handle_paste("hello".to_string()); let needs_redraw = composer.handle_paste("hello".to_string());
assert!(needs_redraw); assert!(needs_redraw);
@@ -908,7 +911,8 @@ mod tests {
let (tx, _rx) = std::sync::mpsc::channel(); let (tx, _rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
let large = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 10); let large = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 10);
let needs_redraw = composer.handle_paste(large.clone()); let needs_redraw = composer.handle_paste(large.clone());
@@ -937,7 +941,8 @@ mod tests {
let large = "y".repeat(LARGE_PASTE_CHAR_THRESHOLD + 1); let large = "y".repeat(LARGE_PASTE_CHAR_THRESHOLD + 1);
let (tx, _rx) = std::sync::mpsc::channel(); let (tx, _rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
composer.handle_paste(large); composer.handle_paste(large);
assert_eq!(composer.pending_pastes.len(), 1); assert_eq!(composer.pending_pastes.len(), 1);
@@ -973,7 +978,12 @@ mod tests {
for (name, input) in test_cases { for (name, input) in test_cases {
// Create a fresh composer for each test case // Create a fresh composer for each test case
let mut composer = ChatComposer::new(true, sender.clone(), false); let mut composer = ChatComposer::new(
true,
sender.clone(),
false,
"Ask Codex to do anything".to_string(),
);
if let Some(text) = input { if let Some(text) = input {
composer.handle_paste(text); composer.handle_paste(text);
@@ -1011,7 +1021,8 @@ mod tests {
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
// Type the slash command. // Type the slash command.
for ch in [ for ch in [
@@ -1054,7 +1065,8 @@ mod tests {
let (tx, rx) = std::sync::mpsc::channel(); let (tx, rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
for ch in ['/', 'm', 'e', 'n', 't', 'i', 'o', 'n'] { for ch in ['/', 'm', 'e', 'n', 't', 'i', 'o', 'n'] {
let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE)); let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
@@ -1093,7 +1105,8 @@ mod tests {
let (tx, _rx) = std::sync::mpsc::channel(); let (tx, _rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
// Define test cases: (paste content, is_large) // Define test cases: (paste content, is_large)
let test_cases = [ let test_cases = [
@@ -1166,7 +1179,8 @@ mod tests {
let (tx, _rx) = std::sync::mpsc::channel(); let (tx, _rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
// Define test cases: (content, is_large) // Define test cases: (content, is_large)
let test_cases = [ let test_cases = [
@@ -1232,7 +1246,8 @@ mod tests {
let (tx, _rx) = std::sync::mpsc::channel(); let (tx, _rx) = std::sync::mpsc::channel();
let sender = AppEventSender::new(tx); let sender = AppEventSender::new(tx);
let mut composer = ChatComposer::new(true, sender, false); let mut composer =
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
// Define test cases: (cursor_position_from_end, expected_pending_count) // Define test cases: (cursor_position_from_end, expected_pending_count)
let test_cases = [ let test_cases = [

View File

@@ -58,6 +58,7 @@ pub(crate) struct BottomPaneParams {
pub(crate) app_event_tx: AppEventSender, pub(crate) app_event_tx: AppEventSender,
pub(crate) has_input_focus: bool, pub(crate) has_input_focus: bool,
pub(crate) enhanced_keys_supported: bool, pub(crate) enhanced_keys_supported: bool,
pub(crate) placeholder_text: String,
} }
impl BottomPane<'_> { impl BottomPane<'_> {
@@ -69,6 +70,7 @@ impl BottomPane<'_> {
params.has_input_focus, params.has_input_focus,
params.app_event_tx.clone(), params.app_event_tx.clone(),
enhanced_keys_supported, enhanced_keys_supported,
params.placeholder_text,
), ),
active_view: None, active_view: None,
app_event_tx: params.app_event_tx, app_event_tx: params.app_event_tx,
@@ -352,6 +354,7 @@ mod tests {
app_event_tx: tx, app_event_tx: tx,
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
pane.push_approval_request(exec_request()); pane.push_approval_request(exec_request());
assert_eq!(CancellationEvent::Handled, pane.on_ctrl_c()); assert_eq!(CancellationEvent::Handled, pane.on_ctrl_c());
@@ -369,6 +372,7 @@ mod tests {
app_event_tx: tx, app_event_tx: tx,
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
// Create an approval modal (active view). // Create an approval modal (active view).
@@ -397,6 +401,7 @@ mod tests {
app_event_tx: tx.clone(), app_event_tx: tx.clone(),
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
// Start a running task so the status indicator replaces the composer. // Start a running task so the status indicator replaces the composer.
@@ -446,6 +451,7 @@ mod tests {
app_event_tx: tx, app_event_tx: tx,
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
// Begin a task: show initial status. // Begin a task: show initial status.
@@ -477,6 +483,7 @@ mod tests {
app_event_tx: tx, app_event_tx: tx,
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
// Activate spinner (status view replaces composer) with no live ring. // Activate spinner (status view replaces composer) with no live ring.
@@ -528,6 +535,7 @@ mod tests {
app_event_tx: tx, app_event_tx: tx,
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
pane.set_task_running(true); pane.set_task_running(true);

View File

@@ -932,33 +932,33 @@ mod tests {
use rand::prelude::*; use rand::prelude::*;
fn rand_grapheme(rng: &mut rand::rngs::StdRng) -> String { fn rand_grapheme(rng: &mut rand::rngs::StdRng) -> String {
let r: u8 = rng.gen_range(0..100); let r: u8 = rng.random_range(0..100);
match r { match r {
0..=4 => "\n".to_string(), 0..=4 => "\n".to_string(),
5..=12 => " ".to_string(), 5..=12 => " ".to_string(),
13..=35 => (rng.gen_range(b'a'..=b'z') as char).to_string(), 13..=35 => (rng.random_range(b'a'..=b'z') as char).to_string(),
36..=45 => (rng.gen_range(b'A'..=b'Z') as char).to_string(), 36..=45 => (rng.random_range(b'A'..=b'Z') as char).to_string(),
46..=52 => (rng.gen_range(b'0'..=b'9') as char).to_string(), 46..=52 => (rng.random_range(b'0'..=b'9') as char).to_string(),
53..=65 => { 53..=65 => {
// Some emoji (wide graphemes) // Some emoji (wide graphemes)
let choices = ["👍", "😊", "🐍", "🚀", "🧪", "🌟"]; let choices = ["👍", "😊", "🐍", "🚀", "🧪", "🌟"];
choices[rng.gen_range(0..choices.len())].to_string() choices[rng.random_range(0..choices.len())].to_string()
} }
66..=75 => { 66..=75 => {
// CJK wide characters // CJK wide characters
let choices = ["", "", "", "", "", "", "", "", ""]; let choices = ["", "", "", "", "", "", "", "", ""];
choices[rng.gen_range(0..choices.len())].to_string() choices[rng.random_range(0..choices.len())].to_string()
} }
76..=85 => { 76..=85 => {
// Combining mark sequences // Combining mark sequences
let base = ["e", "a", "o", "n", "u"][rng.gen_range(0..5)]; let base = ["e", "a", "o", "n", "u"][rng.random_range(0..5)];
let marks = ["\u{0301}", "\u{0308}", "\u{0302}", "\u{0303}"]; let marks = ["\u{0301}", "\u{0308}", "\u{0302}", "\u{0303}"];
format!("{}{}", base, marks[rng.gen_range(0..marks.len())]) format!("{base}{}", marks[rng.random_range(0..marks.len())])
} }
86..=92 => { 86..=92 => {
// Some non-latin single codepoints (Greek, Cyrillic, Hebrew) // Some non-latin single codepoints (Greek, Cyrillic, Hebrew)
let choices = ["Ω", "β", "Ж", "ю", "ש", "م", ""]; let choices = ["Ω", "β", "Ж", "ю", "ש", "م", ""];
choices[rng.gen_range(0..choices.len())].to_string() choices[rng.random_range(0..choices.len())].to_string()
} }
_ => { _ => {
// ZWJ sequences (single graphemes but multi-codepoint) // ZWJ sequences (single graphemes but multi-codepoint)
@@ -967,7 +967,7 @@ mod tests {
"👨\u{200D}💻", // man technologist "👨\u{200D}💻", // man technologist
"🏳️\u{200D}🌈", // rainbow flag "🏳️\u{200D}🌈", // rainbow flag
]; ];
choices[rng.gen_range(0..choices.len())].to_string() choices[rng.random_range(0..choices.len())].to_string()
} }
} }
} }
@@ -1447,7 +1447,7 @@ mod tests {
let mut elem_texts: Vec<String> = Vec::new(); let mut elem_texts: Vec<String> = Vec::new();
let mut next_elem_id: usize = 0; let mut next_elem_id: usize = 0;
// Start with a random base string // Start with a random base string
let base_len = rng.gen_range(0..30); let base_len = rng.random_range(0..30);
let mut base = String::new(); let mut base = String::new();
for _ in 0..base_len { for _ in 0..base_len {
base.push_str(&rand_grapheme(&mut rng)); base.push_str(&rand_grapheme(&mut rng));
@@ -1457,26 +1457,26 @@ mod tests {
let mut boundaries: Vec<usize> = vec![0]; let mut boundaries: Vec<usize> = vec![0];
boundaries.extend(ta.text().char_indices().map(|(i, _)| i).skip(1)); boundaries.extend(ta.text().char_indices().map(|(i, _)| i).skip(1));
boundaries.push(ta.text().len()); boundaries.push(ta.text().len());
let init = boundaries[rng.gen_range(0..boundaries.len())]; let init = boundaries[rng.random_range(0..boundaries.len())];
ta.set_cursor(init); ta.set_cursor(init);
let mut width: u16 = rng.gen_range(1..=12); let mut width: u16 = rng.random_range(1..=12);
let mut height: u16 = rng.gen_range(1..=4); let mut height: u16 = rng.random_range(1..=4);
for _step in 0..200 { for _step in 0..200 {
// Mostly stable width/height, occasionally change // Mostly stable width/height, occasionally change
if rng.gen_bool(0.1) { if rng.random_bool(0.1) {
width = rng.gen_range(1..=12); width = rng.random_range(1..=12);
} }
if rng.gen_bool(0.1) { if rng.random_bool(0.1) {
height = rng.gen_range(1..=4); height = rng.random_range(1..=4);
} }
// Pick an operation // Pick an operation
match rng.gen_range(0..18) { match rng.random_range(0..18) {
0 => { 0 => {
// insert small random string at cursor // insert small random string at cursor
let len = rng.gen_range(0..6); let len = rng.random_range(0..6);
let mut s = String::new(); let mut s = String::new();
for _ in 0..len { for _ in 0..len {
s.push_str(&rand_grapheme(&mut rng)); s.push_str(&rand_grapheme(&mut rng));
@@ -1488,14 +1488,14 @@ mod tests {
let mut b: Vec<usize> = vec![0]; let mut b: Vec<usize> = vec![0];
b.extend(ta.text().char_indices().map(|(i, _)| i).skip(1)); b.extend(ta.text().char_indices().map(|(i, _)| i).skip(1));
b.push(ta.text().len()); b.push(ta.text().len());
let i1 = rng.gen_range(0..b.len()); let i1 = rng.random_range(0..b.len());
let i2 = rng.gen_range(0..b.len()); let i2 = rng.random_range(0..b.len());
let (start, end) = if b[i1] <= b[i2] { let (start, end) = if b[i1] <= b[i2] {
(b[i1], b[i2]) (b[i1], b[i2])
} else { } else {
(b[i2], b[i1]) (b[i2], b[i1])
}; };
let insert_len = rng.gen_range(0..=4); let insert_len = rng.random_range(0..=4);
let mut s = String::new(); let mut s = String::new();
for _ in 0..insert_len { for _ in 0..insert_len {
s.push_str(&rand_grapheme(&mut rng)); s.push_str(&rand_grapheme(&mut rng));
@@ -1520,8 +1520,8 @@ mod tests {
); );
} }
} }
2 => ta.delete_backward(rng.gen_range(0..=3)), 2 => ta.delete_backward(rng.random_range(0..=3)),
3 => ta.delete_forward(rng.gen_range(0..=3)), 3 => ta.delete_forward(rng.random_range(0..=3)),
4 => ta.delete_backward_word(), 4 => ta.delete_backward_word(),
5 => ta.kill_to_beginning_of_line(), 5 => ta.kill_to_beginning_of_line(),
6 => ta.kill_to_end_of_line(), 6 => ta.kill_to_end_of_line(),
@@ -1534,7 +1534,7 @@ mod tests {
13 => { 13 => {
// Insert an element with a unique sentinel payload // Insert an element with a unique sentinel payload
let payload = let payload =
format!("[[EL#{}:{}]]", next_elem_id, rng.gen_range(1000..9999)); format!("[[EL#{}:{}]]", next_elem_id, rng.random_range(1000..9999));
next_elem_id += 1; next_elem_id += 1;
ta.insert_element(&payload); ta.insert_element(&payload);
elem_texts.push(payload); elem_texts.push(payload);
@@ -1545,7 +1545,7 @@ mod tests {
if let Some(start) = ta.text().find(&payload) { if let Some(start) = ta.text().find(&payload) {
let end = start + payload.len(); let end = start + payload.len();
if end - start > 2 { if end - start > 2 {
let pos = rng.gen_range(start + 1..end - 1); let pos = rng.random_range(start + 1..end - 1);
let ins = rand_grapheme(&mut rng); let ins = rand_grapheme(&mut rng);
ta.insert_str_at(pos, &ins); ta.insert_str_at(pos, &ins);
} }
@@ -1558,8 +1558,8 @@ mod tests {
if let Some(start) = ta.text().find(&payload) { if let Some(start) = ta.text().find(&payload) {
let end = start + payload.len(); let end = start + payload.len();
// Create an intersecting range [start-δ, end-δ2) // Create an intersecting range [start-δ, end-δ2)
let mut s = start.saturating_sub(rng.gen_range(0..=2)); let mut s = start.saturating_sub(rng.random_range(0..=2));
let mut e = (end + rng.gen_range(0..=2)).min(ta.text().len()); let mut e = (end + rng.random_range(0..=2)).min(ta.text().len());
// Align to char boundaries to satisfy String::replace_range contract // Align to char boundaries to satisfy String::replace_range contract
let txt = ta.text(); let txt = ta.text();
while s > 0 && !txt.is_char_boundary(s) { while s > 0 && !txt.is_char_boundary(s) {
@@ -1571,7 +1571,7 @@ mod tests {
if s < e { if s < e {
// Small replacement text // Small replacement text
let mut srep = String::new(); let mut srep = String::new();
for _ in 0..rng.gen_range(0..=2) { for _ in 0..rng.random_range(0..=2) {
srep.push_str(&rand_grapheme(&mut rng)); srep.push_str(&rand_grapheme(&mut rng));
} }
ta.replace_range(s..e, &srep); ta.replace_range(s..e, &srep);
@@ -1585,7 +1585,7 @@ mod tests {
if let Some(start) = ta.text().find(&payload) { if let Some(start) = ta.text().find(&payload) {
let end = start + payload.len(); let end = start + payload.len();
if end - start > 2 { if end - start > 2 {
let pos = rng.gen_range(start + 1..end - 1); let pos = rng.random_range(start + 1..end - 1);
ta.set_cursor(pos); ta.set_cursor(pos);
} }
} }
@@ -1593,7 +1593,7 @@ mod tests {
} }
_ => { _ => {
// Jump to word boundaries // Jump to word boundaries
if rng.gen_bool(0.5) { if rng.random_bool(0.5) {
let p = ta.beginning_of_previous_word(); let p = ta.beginning_of_previous_word();
ta.set_cursor(p); ta.set_cursor(p);
} else { } else {

View File

@@ -28,6 +28,7 @@ use codex_core::protocol::TurnDiffEvent;
use codex_protocol::parse_command::ParsedCommand; use codex_protocol::parse_command::ParsedCommand;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use crossterm::event::KeyEventKind; use crossterm::event::KeyEventKind;
use rand::Rng;
use ratatui::buffer::Buffer; use ratatui::buffer::Buffer;
use ratatui::layout::Constraint; use ratatui::layout::Constraint;
use ratatui::layout::Layout; use ratatui::layout::Layout;
@@ -494,6 +495,8 @@ impl ChatWidget<'_> {
initial_images: Vec<PathBuf>, initial_images: Vec<PathBuf>,
enhanced_keys_supported: bool, enhanced_keys_supported: bool,
) -> Self { ) -> Self {
let mut rng = rand::rng();
let placeholder = EXAMPLE_PROMPTS[rng.random_range(0..EXAMPLE_PROMPTS.len())].to_string();
let codex_op_tx = spawn_agent(config.clone(), app_event_tx.clone(), conversation_manager); let codex_op_tx = spawn_agent(config.clone(), app_event_tx.clone(), conversation_manager);
Self { Self {
@@ -503,6 +506,7 @@ impl ChatWidget<'_> {
app_event_tx, app_event_tx,
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported, enhanced_keys_supported,
placeholder_text: placeholder,
}), }),
active_exec_cell: None, active_exec_cell: None,
config: config.clone(), config: config.clone(),
@@ -680,10 +684,6 @@ impl ChatWidget<'_> {
)); ));
} }
pub(crate) fn add_prompts_output(&mut self) {
self.add_to_history(&history_cell::new_prompts_output());
}
/// Forward file-search results to the bottom pane. /// Forward file-search results to the bottom pane.
pub(crate) fn apply_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) { pub(crate) fn apply_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
self.bottom_pane.on_file_search_result(query, matches); self.bottom_pane.on_file_search_result(query, matches);
@@ -764,6 +764,15 @@ impl WidgetRef for &ChatWidget<'_> {
} }
} }
const EXAMPLE_PROMPTS: [&str; 6] = [
"Explain this codebase",
"Summarize recent commits",
"Implement {feature}",
"Find and fix a bug in @filename",
"Write tests for @filename",
"Improve documentation in @filename",
];
fn add_token_usage(current_usage: &TokenUsage, new_usage: &TokenUsage) -> TokenUsage { fn add_token_usage(current_usage: &TokenUsage, new_usage: &TokenUsage) -> TokenUsage {
let cached_input_tokens = match ( let cached_input_tokens = match (
current_usage.cached_input_tokens, current_usage.cached_input_tokens,

View File

@@ -124,6 +124,7 @@ fn make_chatwidget_manual() -> (
app_event_tx: app_event_tx.clone(), app_event_tx: app_event_tx.clone(),
has_input_focus: true, has_input_focus: true,
enhanced_keys_supported: false, enhanced_keys_supported: false,
placeholder_text: "Ask Codex to do anything".to_string(),
}); });
let widget = ChatWidget { let widget = ChatWidget {
app_event_tx, app_event_tx,

View File

@@ -170,7 +170,6 @@ pub(crate) fn new_session_info(
Line::from(format!(" /init - {}", SlashCommand::Init.description()).dim()), Line::from(format!(" /init - {}", SlashCommand::Init.description()).dim()),
Line::from(format!(" /status - {}", SlashCommand::Status.description()).dim()), Line::from(format!(" /status - {}", SlashCommand::Status.description()).dim()),
Line::from(format!(" /diff - {}", SlashCommand::Diff.description()).dim()), Line::from(format!(" /diff - {}", SlashCommand::Diff.description()).dim()),
Line::from(format!(" /prompts - {}", SlashCommand::Prompts.description()).dim()),
Line::from("".dim()), Line::from("".dim()),
]; ];
PlainHistoryCell { lines } PlainHistoryCell { lines }
@@ -635,21 +634,6 @@ pub(crate) fn new_status_output(
PlainHistoryCell { lines } PlainHistoryCell { lines }
} }
pub(crate) fn new_prompts_output() -> PlainHistoryCell {
let lines: Vec<Line<'static>> = vec![
Line::from("/prompts".magenta()),
Line::from(""),
Line::from(" 1. Explain this codebase"),
Line::from(" 2. Summarize recent commits"),
Line::from(" 3. Implement {feature}"),
Line::from(" 4. Find and fix a bug in @filename"),
Line::from(" 5. Write tests for @filename"),
Line::from(" 6. Improve documentation in @filename"),
Line::from(""),
];
PlainHistoryCell { lines }
}
pub(crate) fn new_error_event(message: String) -> PlainHistoryCell { pub(crate) fn new_error_event(message: String) -> PlainHistoryCell {
let lines: Vec<Line<'static>> = vec![vec!["🖐 ".red().bold(), message.into()].into(), "".into()]; let lines: Vec<Line<'static>> = vec![vec!["🖐 ".red().bold(), message.into()].into(), "".into()];
PlainHistoryCell { lines } PlainHistoryCell { lines }

View File

@@ -18,7 +18,6 @@ pub enum SlashCommand {
Diff, Diff,
Mention, Mention,
Status, Status,
Prompts,
Logout, Logout,
Quit, Quit,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@@ -36,7 +35,6 @@ impl SlashCommand {
SlashCommand::Diff => "show git diff (including untracked files)", SlashCommand::Diff => "show git diff (including untracked files)",
SlashCommand::Mention => "mention a file", SlashCommand::Mention => "mention a file",
SlashCommand::Status => "show current session configuration and token usage", SlashCommand::Status => "show current session configuration and token usage",
SlashCommand::Prompts => "show example prompts",
SlashCommand::Logout => "log out of Codex", SlashCommand::Logout => "log out of Codex",
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
SlashCommand::TestApproval => "test approval request", SlashCommand::TestApproval => "test approval request",