mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:23:33 +00:00
fix(tui/history): show run duration and drop wait json
This commit is contained in:
@@ -206,7 +206,7 @@ struct RunningCommand {
|
||||
stderr: String,
|
||||
wait_total: Option<Duration>,
|
||||
wait_active: bool,
|
||||
wait_note: Option<(String, bool)>,
|
||||
wait_notes: Vec<(String, bool)>,
|
||||
}
|
||||
|
||||
pub(crate) struct ChatWidget<'a> {
|
||||
@@ -4444,7 +4444,7 @@ impl ChatWidget<'_> {
|
||||
|
||||
if let Some(running) = self.exec.running_commands.get_mut(&exec_call_id) {
|
||||
running.wait_active = true;
|
||||
running.wait_note = None;
|
||||
running.wait_notes.clear();
|
||||
let history_index = running.history_index;
|
||||
if let Some(idx) = history_index {
|
||||
if idx < self.history_cells.len() {
|
||||
@@ -4453,7 +4453,7 @@ impl ChatWidget<'_> {
|
||||
.downcast_mut::<history_cell::ExecCell>()
|
||||
{
|
||||
exec_cell.set_waiting(true);
|
||||
exec_cell.set_wait_note(None);
|
||||
exec_cell.clear_wait_notes();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4544,11 +4544,27 @@ impl ChatWidget<'_> {
|
||||
{
|
||||
let trimmed = content.trim();
|
||||
let wait_still_pending = !success && trimmed != "Cancelled by user.";
|
||||
let wait_note = if trimmed == "Cancelled by user." {
|
||||
Some(("Wait cancelled".to_string(), true))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut note_lines: Vec<(String, bool)> = Vec::new();
|
||||
let suppress_json_notes = serde_json::from_str::<serde_json::Value>(
|
||||
trimmed,
|
||||
)
|
||||
.ok()
|
||||
.and_then(|value| {
|
||||
value.as_object().map(|obj| {
|
||||
obj.contains_key("output") || obj.contains_key("metadata")
|
||||
})
|
||||
})
|
||||
.unwrap_or(false);
|
||||
if !suppress_json_notes {
|
||||
for line in content.lines() {
|
||||
let note_text = line.trim();
|
||||
if note_text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let is_error_note = note_text == "Cancelled by user.";
|
||||
note_lines.push((note_text.to_string(), is_error_note));
|
||||
}
|
||||
}
|
||||
let mut history_index: Option<usize> = None;
|
||||
if let Some(running) = self.exec.running_commands.get_mut(&exec_call_id) {
|
||||
let base = running.wait_total.unwrap_or_default();
|
||||
@@ -4556,7 +4572,17 @@ impl ChatWidget<'_> {
|
||||
running.wait_total = Some(total);
|
||||
history_index = running.history_index;
|
||||
running.wait_active = wait_still_pending;
|
||||
running.wait_note = wait_note.clone();
|
||||
for (text, is_error_note) in ¬e_lines {
|
||||
if running
|
||||
.wait_notes
|
||||
.last()
|
||||
.map(|(existing, existing_err)| existing == text && existing_err == is_error_note)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
running.wait_notes.push((text.clone(), *is_error_note));
|
||||
}
|
||||
}
|
||||
|
||||
let mut updated = false;
|
||||
@@ -4576,7 +4602,9 @@ impl ChatWidget<'_> {
|
||||
} else {
|
||||
exec_cell.set_waiting(false);
|
||||
}
|
||||
exec_cell.set_wait_note(wait_note.clone());
|
||||
for (text, is_error_note) in ¬e_lines {
|
||||
exec_cell.push_wait_note(text, *is_error_note);
|
||||
}
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
@@ -4601,7 +4629,9 @@ impl ChatWidget<'_> {
|
||||
} else {
|
||||
exec_cell.set_waiting(false);
|
||||
}
|
||||
exec_cell.set_wait_note(wait_note.clone());
|
||||
for (text, is_error_note) in ¬e_lines {
|
||||
exec_cell.push_wait_note(text, *is_error_note);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -510,7 +510,7 @@ pub(super) fn handle_exec_begin_now(
|
||||
stderr: String::new(),
|
||||
wait_total: None,
|
||||
wait_active: false,
|
||||
wait_note: None,
|
||||
wait_notes: Vec::new(),
|
||||
},
|
||||
);
|
||||
chat.invalidate_height_cache();
|
||||
@@ -544,7 +544,7 @@ pub(super) fn handle_exec_begin_now(
|
||||
stderr: String::new(),
|
||||
wait_total: None,
|
||||
wait_active: false,
|
||||
wait_note: None,
|
||||
wait_notes: Vec::new(),
|
||||
},
|
||||
);
|
||||
if !chat.tools_state.running_web_search.is_empty() {
|
||||
@@ -593,7 +593,7 @@ pub(super) fn handle_exec_end_now(
|
||||
let ExecCommandEndEvent {
|
||||
call_id,
|
||||
exit_code,
|
||||
duration: _,
|
||||
duration,
|
||||
stdout,
|
||||
stderr,
|
||||
} = ev;
|
||||
@@ -604,17 +604,17 @@ pub(super) fn handle_exec_end_now(
|
||||
chat.height_manager
|
||||
.borrow_mut()
|
||||
.record_event(HeightEvent::RunEnd);
|
||||
let (command, parsed, history_index, explore_entry, wait_total, wait_note) = match cmd {
|
||||
let (command, parsed, history_index, explore_entry, wait_total, wait_notes) = match cmd {
|
||||
Some(super::RunningCommand {
|
||||
command,
|
||||
parsed,
|
||||
history_index,
|
||||
explore_entry,
|
||||
wait_total,
|
||||
wait_note,
|
||||
wait_notes,
|
||||
..
|
||||
}) => (command, parsed, history_index, explore_entry, wait_total, wait_note),
|
||||
None => (vec![call_id.clone()], vec![], None, None, None, None),
|
||||
}) => (command, parsed, history_index, explore_entry, wait_total, wait_notes),
|
||||
None => (vec![call_id.clone()], vec![], None, None, None, Vec::new()),
|
||||
};
|
||||
|
||||
if let Some((agg_idx, entry_idx)) = explore_entry {
|
||||
@@ -684,8 +684,9 @@ pub(super) fn handle_exec_end_now(
|
||||
));
|
||||
if let Some(ref cell) = completed_opt {
|
||||
cell.set_wait_total(wait_total);
|
||||
cell.set_wait_note(wait_note.clone());
|
||||
cell.set_wait_notes(&wait_notes);
|
||||
cell.set_waiting(false);
|
||||
cell.set_run_duration(Some(duration));
|
||||
}
|
||||
|
||||
let mut replaced = false;
|
||||
|
||||
@@ -1331,8 +1331,9 @@ struct ExecWaitNote {
|
||||
#[derive(Clone, Default)]
|
||||
struct ExecWaitState {
|
||||
total_wait: Option<Duration>,
|
||||
run_duration: Option<Duration>,
|
||||
waiting: bool,
|
||||
note: Option<ExecWaitNote>,
|
||||
notes: Vec<ExecWaitNote>,
|
||||
}
|
||||
|
||||
pub(crate) struct ExecCell {
|
||||
@@ -1985,35 +1986,95 @@ impl ExecCell {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wait_total(&self) -> Option<Duration> {
|
||||
self.wait_state.borrow().total_wait
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_note(&self, note: Option<(String, bool)>) {
|
||||
let new_note = note.map(|(text, is_error)| ExecWaitNote { text, is_error });
|
||||
pub(crate) fn set_run_duration(&self, duration: Option<Duration>) {
|
||||
let mut state = self.wait_state.borrow_mut();
|
||||
if state.note != new_note {
|
||||
state.note = new_note;
|
||||
if state.run_duration != duration {
|
||||
state.run_duration = duration;
|
||||
drop(state);
|
||||
self.invalidate_render_caches();
|
||||
}
|
||||
}
|
||||
|
||||
fn wait_note_line(&self, state: &ExecWaitState) -> Option<Line<'static>> {
|
||||
let note = state.note.as_ref()?;
|
||||
let mut line = Line::from(note.text.clone());
|
||||
let mut style = Style::default().fg(if note.is_error {
|
||||
crate::colors::error()
|
||||
} else {
|
||||
crate::colors::text_dim()
|
||||
pub(crate) fn wait_total(&self) -> Option<Duration> {
|
||||
self.wait_state.borrow().total_wait
|
||||
}
|
||||
|
||||
pub(crate) fn clear_wait_notes(&self) {
|
||||
let mut state = self.wait_state.borrow_mut();
|
||||
if state.notes.is_empty() {
|
||||
return;
|
||||
}
|
||||
state.notes.clear();
|
||||
drop(state);
|
||||
self.invalidate_render_caches();
|
||||
}
|
||||
|
||||
pub(crate) fn push_wait_note(&self, text: &str, is_error: bool) {
|
||||
let trimmed = text.trim();
|
||||
if trimmed.is_empty() {
|
||||
return;
|
||||
}
|
||||
let mut state = self.wait_state.borrow_mut();
|
||||
if state
|
||||
.notes
|
||||
.last()
|
||||
.map(|note| note.text == trimmed && note.is_error == is_error)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
state.notes.push(ExecWaitNote {
|
||||
text: trimmed.to_string(),
|
||||
is_error,
|
||||
});
|
||||
if note.is_error {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
drop(state);
|
||||
self.invalidate_render_caches();
|
||||
}
|
||||
|
||||
pub(crate) fn set_wait_notes(&self, notes: &[(String, bool)]) {
|
||||
let mut state = self.wait_state.borrow_mut();
|
||||
let mut changed = state.notes.len() != notes.len();
|
||||
if !changed {
|
||||
for (existing, (text, is_error)) in state.notes.iter().zip(notes.iter()) {
|
||||
if existing.text != text.trim() || existing.is_error != *is_error {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for span in line.spans.iter_mut() {
|
||||
span.style = style;
|
||||
if !changed {
|
||||
return;
|
||||
}
|
||||
Some(line)
|
||||
state.notes = notes
|
||||
.iter()
|
||||
.map(|(text, is_error)| ExecWaitNote {
|
||||
text: text.trim().to_string(),
|
||||
is_error: *is_error,
|
||||
})
|
||||
.filter(|note| !note.text.is_empty())
|
||||
.collect();
|
||||
drop(state);
|
||||
self.invalidate_render_caches();
|
||||
}
|
||||
|
||||
fn wait_note_lines(&self, state: &ExecWaitState) -> Vec<Line<'static>> {
|
||||
let mut lines = Vec::new();
|
||||
for note in &state.notes {
|
||||
let mut line = Line::from(note.text.clone());
|
||||
let mut style = Style::default().fg(if note.is_error {
|
||||
crate::colors::error()
|
||||
} else {
|
||||
crate::colors::text_dim()
|
||||
});
|
||||
if note.is_error {
|
||||
style = style.add_modifier(Modifier::BOLD);
|
||||
}
|
||||
for span in line.spans.iter_mut() {
|
||||
span.style = style;
|
||||
}
|
||||
lines.push(line);
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
fn wait_state_snapshot(&self) -> ExecWaitState {
|
||||
@@ -2024,6 +2085,16 @@ impl ExecCell {
|
||||
if state.waiting {
|
||||
return None;
|
||||
}
|
||||
if let Some(run_duration) = state.run_duration {
|
||||
if run_duration.is_zero() {
|
||||
return None;
|
||||
}
|
||||
let text = format!("Ran for {}", format_duration(run_duration));
|
||||
return Some(Line::styled(
|
||||
text,
|
||||
Style::default().fg(crate::colors::text_dim()),
|
||||
));
|
||||
}
|
||||
let total = state.total_wait?;
|
||||
if total.is_zero() {
|
||||
return None;
|
||||
@@ -2160,9 +2231,7 @@ impl ExecCell {
|
||||
if let Some(summary_line) = self.wait_summary_line(&wait_state) {
|
||||
extra_lines.push(summary_line);
|
||||
}
|
||||
if let Some(note_line) = self.wait_note_line(&wait_state) {
|
||||
extra_lines.push(note_line);
|
||||
}
|
||||
extra_lines.extend(self.wait_note_lines(&wait_state));
|
||||
if !extra_lines.is_empty() {
|
||||
let is_blank_line = |line: &Line<'static>| {
|
||||
line.spans
|
||||
@@ -2175,34 +2244,26 @@ impl ExecCell {
|
||||
.map(|span| span.content.as_ref().starts_with("Error (exit code"))
|
||||
.unwrap_or(false)
|
||||
};
|
||||
if let Some(pos) = out.iter().position(is_error_line) {
|
||||
let mut insert_at = pos;
|
||||
if insert_at > 0 && !is_blank_line(&out[insert_at - 1]) {
|
||||
out.insert(insert_at, Line::from(""));
|
||||
insert_at += 1;
|
||||
}
|
||||
for line in extra_lines.iter() {
|
||||
out.insert(insert_at, line.clone());
|
||||
insert_at += 1;
|
||||
out.insert(insert_at, Line::from(""));
|
||||
insert_at += 1;
|
||||
let insert_at = if let Some(pos) = out.iter().position(is_error_line) {
|
||||
pos
|
||||
} else {
|
||||
out.len()
|
||||
};
|
||||
|
||||
let mut block: Vec<Line<'static>> = Vec::new();
|
||||
if insert_at > 0 && !is_blank_line(&out[insert_at - 1]) {
|
||||
block.push(Line::from(""));
|
||||
}
|
||||
block.extend(extra_lines.into_iter());
|
||||
if insert_at < out.len() {
|
||||
if !is_blank_line(&out[insert_at]) {
|
||||
block.push(Line::from(""));
|
||||
}
|
||||
} else {
|
||||
let mut insert_at = out.len();
|
||||
if insert_at > 0 && is_blank_line(&out[insert_at.saturating_sub(1)]) {
|
||||
insert_at = insert_at.saturating_sub(1);
|
||||
}
|
||||
if insert_at > 0 && !is_blank_line(&out[insert_at - 1]) {
|
||||
out.insert(insert_at, Line::from(""));
|
||||
insert_at += 1;
|
||||
}
|
||||
for line in extra_lines.iter() {
|
||||
out.insert(insert_at, line.clone());
|
||||
insert_at += 1;
|
||||
out.insert(insert_at, Line::from(""));
|
||||
insert_at += 1;
|
||||
}
|
||||
block.push(Line::from(""));
|
||||
}
|
||||
|
||||
out.splice(insert_at..insert_at, block);
|
||||
}
|
||||
*self.cached_pre_lines.borrow_mut() = Some(pre.clone());
|
||||
*self.cached_out_lines.borrow_mut() = Some(out.clone());
|
||||
|
||||
Reference in New Issue
Block a user