feat: compactのプログレス表示
This commit is contained in:
parent
ec1eccd10d
commit
8ebdd47fbb
|
|
@ -568,9 +568,7 @@ impl App {
|
||||||
}
|
}
|
||||||
Event::ToolCallDone { id, arguments, .. } => {
|
Event::ToolCallDone { id, arguments, .. } => {
|
||||||
self.current_tool = None;
|
self.current_tool = None;
|
||||||
let name = self
|
let name = self.find_tool_call_mut(&id).map(|b| b.name.clone());
|
||||||
.find_tool_call_mut(&id)
|
|
||||||
.map(|b| b.name.clone());
|
|
||||||
if let Some(name) = name.as_deref() {
|
if let Some(name) = name.as_deref() {
|
||||||
self.task_store.apply_tool_call(name, &arguments);
|
self.task_store.apply_tool_call(name, &arguments);
|
||||||
}
|
}
|
||||||
|
|
@ -679,15 +677,47 @@ impl App {
|
||||||
self.assistant_streaming = false;
|
self.assistant_streaming = false;
|
||||||
}
|
}
|
||||||
Event::CompactStart => {
|
Event::CompactStart => {
|
||||||
self.blocks.push(Block::Compact(CompactEvent::Start));
|
self.blocks.push(Block::Compact(CompactEvent::Streaming {
|
||||||
|
started_at: Instant::now(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
Event::CompactDone { new_session_id } => {
|
Event::CompactDone { new_session_id } => {
|
||||||
self.blocks
|
if let Some(evt) = self.last_streaming_compact_mut() {
|
||||||
.push(Block::Compact(CompactEvent::Done { new_session_id }));
|
let elapsed_secs = match evt {
|
||||||
|
CompactEvent::Streaming { started_at } => {
|
||||||
|
Some(started_at.elapsed().as_secs())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
*evt = CompactEvent::Done {
|
||||||
|
new_session_id,
|
||||||
|
elapsed_secs,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.blocks.push(Block::Compact(CompactEvent::Done {
|
||||||
|
new_session_id,
|
||||||
|
elapsed_secs: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::CompactFailed { error } => {
|
Event::CompactFailed { error } => {
|
||||||
self.blocks
|
if let Some(evt) = self.last_streaming_compact_mut() {
|
||||||
.push(Block::Compact(CompactEvent::Failed { error }));
|
let elapsed_secs = match evt {
|
||||||
|
CompactEvent::Streaming { started_at } => {
|
||||||
|
Some(started_at.elapsed().as_secs())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
*evt = CompactEvent::Failed {
|
||||||
|
error,
|
||||||
|
elapsed_secs,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.blocks.push(Block::Compact(CompactEvent::Failed {
|
||||||
|
error,
|
||||||
|
elapsed_secs: None,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Event::Alert(alert) => {
|
Event::Alert(alert) => {
|
||||||
self.blocks.push(Block::Alert {
|
self.blocks.push(Block::Alert {
|
||||||
|
|
@ -719,6 +749,7 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Shutdown => {
|
Event::Shutdown => {
|
||||||
|
self.mark_orphan_compacts_incomplete();
|
||||||
self.quit = true;
|
self.quit = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -775,6 +806,33 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn last_streaming_compact_mut(&mut self) -> Option<&mut CompactEvent> {
|
||||||
|
for b in self.blocks.iter_mut().rev() {
|
||||||
|
match b {
|
||||||
|
Block::Compact(evt) if matches!(evt, CompactEvent::Streaming { .. }) => {
|
||||||
|
return Some(evt);
|
||||||
|
}
|
||||||
|
Block::Compact(_) => return None,
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn mark_orphan_compacts_incomplete(&mut self) {
|
||||||
|
for b in self.blocks.iter_mut().rev() {
|
||||||
|
if let Block::Compact(evt) = b {
|
||||||
|
if let CompactEvent::Streaming { started_at } = evt {
|
||||||
|
*evt = CompactEvent::Incomplete {
|
||||||
|
elapsed_secs: Some(started_at.elapsed().as_secs()),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_tool_call_mut(&mut self, id: &str) -> Option<&mut ToolCallBlock> {
|
fn find_tool_call_mut(&mut self, id: &str) -> Option<&mut ToolCallBlock> {
|
||||||
for b in self.blocks.iter_mut().rev() {
|
for b in self.blocks.iter_mut().rev() {
|
||||||
if let Block::ToolCall(tc) = b
|
if let Block::ToolCall(tc) = b
|
||||||
|
|
@ -1310,6 +1368,66 @@ mod completion_flow_tests {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_done_replaces_live_block() {
|
||||||
|
let mut app = App::new("test".into());
|
||||||
|
let id = uuid::Uuid::parse_str("12345678-1234-5678-1234-567812345678").unwrap();
|
||||||
|
|
||||||
|
app.handle_pod_event(Event::CompactStart);
|
||||||
|
app.handle_pod_event(Event::CompactDone { new_session_id: id });
|
||||||
|
|
||||||
|
assert_eq!(compact_block_count(&app), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
app.blocks.as_slice(),
|
||||||
|
[Block::Compact(CompactEvent::Done {
|
||||||
|
new_session_id,
|
||||||
|
elapsed_secs: Some(_),
|
||||||
|
})] if *new_session_id == id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compact_failed_replaces_live_block() {
|
||||||
|
let mut app = App::new("test".into());
|
||||||
|
|
||||||
|
app.handle_pod_event(Event::CompactStart);
|
||||||
|
app.handle_pod_event(Event::CompactFailed {
|
||||||
|
error: "provider 429".into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(compact_block_count(&app), 1);
|
||||||
|
assert!(matches!(
|
||||||
|
app.blocks.as_slice(),
|
||||||
|
[Block::Compact(CompactEvent::Failed {
|
||||||
|
error,
|
||||||
|
elapsed_secs: Some(_),
|
||||||
|
})] if error == "provider 429"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shutdown_marks_live_compact_incomplete() {
|
||||||
|
let mut app = App::new("test".into());
|
||||||
|
|
||||||
|
app.handle_pod_event(Event::CompactStart);
|
||||||
|
app.handle_pod_event(Event::Shutdown);
|
||||||
|
|
||||||
|
assert!(app.quit);
|
||||||
|
assert!(matches!(
|
||||||
|
app.blocks.as_slice(),
|
||||||
|
[Block::Compact(CompactEvent::Incomplete {
|
||||||
|
elapsed_secs: Some(_),
|
||||||
|
})]
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compact_block_count(app: &App) -> usize {
|
||||||
|
app.blocks
|
||||||
|
.iter()
|
||||||
|
.filter(|block| matches!(block, Block::Compact(_)))
|
||||||
|
.count()
|
||||||
|
}
|
||||||
|
|
||||||
fn test_greeting() -> protocol::Greeting {
|
fn test_greeting() -> protocol::Greeting {
|
||||||
protocol::Greeting {
|
protocol::Greeting {
|
||||||
pod_name: "test".into(),
|
pod_name: "test".into(),
|
||||||
|
|
|
||||||
|
|
@ -78,9 +78,21 @@ pub enum ThinkingState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum CompactEvent {
|
pub enum CompactEvent {
|
||||||
Start,
|
/// Live block: compaction worker is running. `started_at` powers the
|
||||||
Done { new_session_id: uuid::Uuid },
|
/// `Compacting... (Xs)` live timer.
|
||||||
Failed { error: String },
|
Streaming { started_at: Instant },
|
||||||
|
/// Compaction ended cleanly with `CompactDone`.
|
||||||
|
Done {
|
||||||
|
new_session_id: uuid::Uuid,
|
||||||
|
elapsed_secs: Option<u64>,
|
||||||
|
},
|
||||||
|
/// Compaction ended with `CompactFailed`.
|
||||||
|
Failed {
|
||||||
|
error: String,
|
||||||
|
elapsed_secs: Option<u64>,
|
||||||
|
},
|
||||||
|
/// The TUI stopped observing events before a terminal compact event.
|
||||||
|
Incomplete { elapsed_secs: Option<u64> },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToolCallBlock {
|
pub struct ToolCallBlock {
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,7 @@ async fn run_loop(
|
||||||
Some(ev) => app.handle_pod_event(ev),
|
Some(ev) => app.handle_pod_event(ev),
|
||||||
None => {
|
None => {
|
||||||
app.connected = false;
|
app.connected = false;
|
||||||
|
app.mark_orphan_compacts_incomplete();
|
||||||
app.push_error("Connection lost");
|
app.push_error("Connection lost");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1011,21 +1011,45 @@ fn fmt_elapsed(secs: u64) -> String {
|
||||||
|
|
||||||
fn render_compact(lines: &mut Vec<Line<'static>>, evt: &CompactEvent, width: u16, mode: Mode) {
|
fn render_compact(lines: &mut Vec<Line<'static>>, evt: &CompactEvent, width: u16, mode: Mode) {
|
||||||
let (text, kind) = match evt {
|
let (text, kind) = match evt {
|
||||||
CompactEvent::Start => ("[compact] starting".to_owned(), MessageKind::NoticeWarn),
|
CompactEvent::Streaming { started_at } => {
|
||||||
CompactEvent::Done { new_session_id } => {
|
let secs = started_at.elapsed().as_secs();
|
||||||
|
(
|
||||||
|
format!("Compacting... ({})", fmt_elapsed(secs)),
|
||||||
|
MessageKind::NoticeWarn,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CompactEvent::Done {
|
||||||
|
new_session_id,
|
||||||
|
elapsed_secs,
|
||||||
|
} => {
|
||||||
let short = new_session_id
|
let short = new_session_id
|
||||||
.to_string()
|
.to_string()
|
||||||
.chars()
|
.chars()
|
||||||
.take(8)
|
.take(8)
|
||||||
.collect::<String>();
|
.collect::<String>();
|
||||||
|
let elapsed = elapsed_suffix(*elapsed_secs);
|
||||||
(
|
(
|
||||||
format!("[compact] done (new session {short})"),
|
format!("[compact] done (new session {short}){elapsed}"),
|
||||||
MessageKind::NoticeWarn,
|
MessageKind::NoticeWarn,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CompactEvent::Failed { error } => {
|
CompactEvent::Failed {
|
||||||
(format!("[compact error] {error}"), MessageKind::NoticeError)
|
error,
|
||||||
|
elapsed_secs,
|
||||||
|
} => {
|
||||||
|
let elapsed = elapsed_suffix(*elapsed_secs);
|
||||||
|
(
|
||||||
|
format!("[compact error] {error}{elapsed}"),
|
||||||
|
MessageKind::NoticeError,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
CompactEvent::Incomplete { elapsed_secs } => match elapsed_secs {
|
||||||
|
Some(s) => (
|
||||||
|
format!("[compact] interrupted ({})", fmt_elapsed(*s)),
|
||||||
|
MessageKind::NoticeError,
|
||||||
|
),
|
||||||
|
None => ("[compact] interrupted".to_owned(), MessageKind::NoticeError),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Overview => push_overview_line(lines, &text, width, kind, ""),
|
Mode::Overview => push_overview_line(lines, &text, width, kind, ""),
|
||||||
|
|
@ -1033,6 +1057,12 @@ fn render_compact(lines: &mut Vec<Line<'static>>, evt: &CompactEvent, width: u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn elapsed_suffix(elapsed_secs: Option<u64>) -> String {
|
||||||
|
elapsed_secs
|
||||||
|
.map(|s| format!(" ({})", fmt_elapsed(s)))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
fn draw_separator(frame: &mut Frame, area: Rect) {
|
fn draw_separator(frame: &mut Frame, area: Rect) {
|
||||||
let line = "─".repeat(area.width as usize);
|
let line = "─".repeat(area.width as usize);
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user