Auto-commit pending changes on agentydragon-task/13-interactive-prompt-during-execution

This commit is contained in:
Rai (Michael Pokorny)
2025-06-24 14:31:38 -07:00
parent 2f0109faeb
commit 22d7b327a0
2 changed files with 62 additions and 7 deletions

View File

@@ -4,8 +4,8 @@
## Status
**General Status**: Not started
**Summary**: Not started; missing Implementation details (How it was implemented and How it works).
**General Status**: Done
**Summary**: Implemented interactive prompt overlay allowing user input during streaming without aborting runs.
## Goal
@@ -23,10 +23,12 @@ Allow users to interleave composing prompts and issuing slash-commands while the
## Implementation
**How it was implemented**
*(Not implemented yet)*
- Modified `BottomPane::handle_key_event` in `tui/src/bottom_pane/mod.rs` to special-case the `StatusIndicatorView` while `is_task_running`, forwarding key events to `ChatComposer` and preserving the overlay.
- Updated `BottomPane::render_ref` to always render the composer first and then overlay the active view, ensuring the input box remains visible and editable under the status indicator.
- Added unit tests in `tui/src/bottom_pane/mod.rs` to verify input is forwarded during task execution and that the status indicator overlay is removed upon task completion.
**How it works**
*(Not implemented yet)*
During LLM streaming or tool execution, the `StatusIndicatorView` remains active as an overlay. The modified event handler detects this overlay and forwards user key events to the underlying `ChatComposer` without dismissing the overlay. On task completion (`set_task_running(false)`), the overlay is automatically removed (via `should_hide_when_task_is_done`), returning to the normal input-only view.
## Notes

View File

@@ -63,6 +63,16 @@ impl BottomPane<'_> {
/// Forward a key event to the active view or the composer.
pub fn handle_key_event(&mut self, key_event: KeyEvent) -> InputResult {
if let Some(mut view) = self.active_view.take() {
// During task execution, allow input to pass through status indicator overlay
if self.is_task_running && view.should_hide_when_task_is_done() {
// restore overlay view and forward input to composer
self.active_view = Some(view);
let (input_result, needs_redraw) = self.composer.handle_key_event(key_event);
if needs_redraw {
self.request_redraw();
}
return input_result;
}
view.handle_key_event(self, key_event);
if !view.is_complete() {
self.active_view = Some(view);
@@ -197,11 +207,54 @@ impl BottomPane<'_> {
impl WidgetRef for &BottomPane<'_> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
// Show BottomPaneView if present.
// Always render composer, then overlay any active view (e.g., status indicator or modal)
(&self.composer).render_ref(area, buf);
if let Some(ov) = &self.active_view {
ov.render(area, buf);
} else {
(&self.composer).render_ref(area, buf);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
/// Construct a BottomPane with default parameters for testing.
fn make_pane() -> BottomPane<'static> {
let (tx, _rx) = std::sync::mpsc::channel();
let app_event_tx = AppEventSender::new(tx);
BottomPane::new(BottomPaneParams {
app_event_tx,
has_input_focus: true,
composer_max_rows: 3,
})
}
#[test]
fn forward_input_during_status_indicator() {
let mut pane = make_pane();
// Start task to show status indicator overlay
pane.set_task_running(true);
// Simulate typing 'h'
let key = KeyEvent::new(KeyCode::Char('h'), KeyModifiers::NONE);
let result = pane.handle_key_event(key);
// No submission event is returned
assert!(matches!(result, InputResult::None));
// Composer should have recorded the input
let content = pane.composer.textarea.lines().join("\n");
assert_eq!(content, "h");
// Status indicator overlay remains active
assert!(pane.active_view.is_some());
}
#[test]
fn remove_status_indicator_after_task_complete() {
let mut pane = make_pane();
pane.set_task_running(true);
assert!(pane.active_view.is_some());
pane.set_task_running(false);
// Overlay should be removed when task finishes
assert!(pane.active_view.is_none());
}
}