Skip to content

Commit

Permalink
restore non-interactive mode, add scrollbar to textarea widget
Browse files Browse the repository at this point in the history
  • Loading branch information
aprxi committed Sep 14, 2024
1 parent c6f3675 commit 4dadeee
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 97 deletions.
151 changes: 78 additions & 73 deletions lumni/src/apps/builtin/llm/prompt/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ use ratatui::Terminal;
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
use tokio::signal;
use tokio::sync::Mutex;
use tokio::time::Duration;
use tokio::time::{timeout, Duration};

use super::chat::db::ConversationDatabase;
use super::chat::{
prompt_app, App, PromptInstruction, PromptInstructionBuilder,
prompt_app, App, ChatEvent, PromptInstruction, PromptInstructionBuilder,
ThreadedChatSession,
};
use super::cli::{
Expand Down Expand Up @@ -196,8 +196,6 @@ async fn process_non_interactive_input(
db_conn: Arc<ConversationDatabase>,
question: Option<String>,
) -> Result<(), ApplicationError> {
return Ok(());

let instruction = match prompt_instruction {
Some(instruction) => instruction,
None => {
Expand All @@ -210,6 +208,11 @@ async fn process_non_interactive_input(
let db_handler = db_conn
.get_conversation_handler(Some(instruction.get_conversation_id()));
let chat = Arc::new(Mutex::new(ThreadedChatSession::new(instruction)));
// Initialize the chat session
{
let mut chat_locked = chat.lock().await;
chat_locked.initialize(db_handler.clone()).await?;
}

// Shared state for handling Ctrl+C
let running = Arc::new(Mutex::new(true));
Expand Down Expand Up @@ -249,75 +252,77 @@ async fn process_non_interactive_input(

let chat_clone = chat.clone();

// // Process the prompt
// let process_handle = tokio::spawn(async move {
// chat_clone.lock().await.message(&input).await?;
//
// let mut receiver = chat_clone.lock().await.subscribe();
// while let Ok(event) = receiver.recv().await {
// match event {
// ChatEvent::ResponseUpdate(content) => {
// print!("{}", content);
// std::io::stdout().flush().unwrap();
// }
// ChatEvent::FinalResponse => break,
// ChatEvent::Error(e) => {
// return Err(ApplicationError::Unexpected(e));
// }
// }
// }
// Ok(())
// });
//
// // Wait for the process to complete or for a shutdown signal
// loop {
// if *shutdown_signal.lock().await {
// // Shutdown signal received, set a timeout for graceful shutdown
// const GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
// match timeout(GRACEFUL_SHUTDOWN_TIMEOUT, process_handle).await {
// Ok(Ok(_)) => {
// eprintln!(
// "Processing completed successfully during shutdown."
// );
// chat.lock().await.stop();
// return Ok(());
// }
// Ok(Err(e)) => {
// eprintln!("Process error during shutdown: {}", e);
// chat.lock().await.stop();
// return Err(ApplicationError::Unexpected(format!(
// "Process error: {}",
// e
// )));
// }
// Err(_) => {
// eprintln!("Graceful shutdown timed out. Forcing exit...");
// chat.lock().await.stop();
// return Ok(());
// }
// }
// }
//
// // Check if the process has completed naturally
// if process_handle.is_finished() {
// process_handle
// .await
// .map_err(|e| {
// ApplicationError::Unexpected(format!("Join error: {}", e))
// })?
// .map_err(|e| {
// ApplicationError::Unexpected(format!(
// "Process error: {}",
// e
// ))
// })?;
// chat.lock().await.stop();
// return Ok(());
// }
//
// // Wait a bit before checking again
// tokio::time::sleep(Duration::from_millis(100)).await;
// }
// Process the prompt
let process_handle = tokio::spawn(async move {
let mut chat_locked = chat_clone.lock().await;

chat_locked.message(&input).await?;

if let Some(mut event_receiver) = chat_locked.event_receiver.take() {
while let Some(event) = event_receiver.recv().await {
match event {
ChatEvent::ResponseUpdate(content) => {
print!("{}", content);
std::io::stdout().flush().unwrap();
}
ChatEvent::FinalResponse => break,
ChatEvent::Error(e) => {
return Err(ApplicationError::Unexpected(e));
}
}
}
}
Ok(())
});
// Wait for the process to complete or for a shutdown signal
loop {
if *shutdown_signal.lock().await {
// Shutdown signal received, set a timeout for graceful shutdown
const GRACEFUL_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(3);
match timeout(GRACEFUL_SHUTDOWN_TIMEOUT, process_handle).await {
Ok(Ok(_)) => {
eprintln!(
"Processing completed successfully during shutdown."
);
chat.lock().await.stop();
return Ok(());
}
Ok(Err(e)) => {
eprintln!("Process error during shutdown: {}", e);
chat.lock().await.stop();
return Err(ApplicationError::Unexpected(format!(
"Process error: {}",
e
)));
}
Err(_) => {
eprintln!("Graceful shutdown timed out. Forcing exit...");
chat.lock().await.stop();
return Ok(());
}
}
}

// Check if the process has completed naturally
if process_handle.is_finished() {
process_handle
.await
.map_err(|e| {
ApplicationError::Unexpected(format!("Join error: {}", e))
})?
.map_err(|e| {
ApplicationError::Unexpected(format!(
"Process error: {}",
e
))
})?;
chat.lock().await.stop();
return Ok(());
}

// Wait a bit before checking again
tokio::time::sleep(Duration::from_millis(100)).await;
}
}

async fn handle_ctrl_c(r: Arc<Mutex<bool>>, s: Arc<Mutex<bool>>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ use super::widgets::{ListWidget, ListWidgetState};
use super::{
ApplicationError, ChatSessionManager, Conversation, ConversationDbHandler,
ConversationEvent, ConversationId, ConversationStatus, KeyTrack,
ModalEvent, ModalWindowTrait, ModalWindowType, PromptInstruction,
ThreadedChatSession, UserEvent, WindowMode,
ModalEvent, ModalWindowTrait, ModalWindowType, UserEvent, WindowMode,
};
pub use crate::external as lumni;

Expand Down Expand Up @@ -86,11 +85,7 @@ impl ConversationListModal {
ConversationEvent::PromptRead,
)));
}
KeyCode::Up => {
return Ok(WindowMode::Modal(ModalEvent::Open(
ModalWindowType::FileBrowser,
)));
}
KeyCode::Up => {}
_ => {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,13 +188,13 @@ impl ModalWindowTrait for FileBrowserModal {
if current_key.modifiers == KeyModifiers::SHIFT {
match current_key.code {
KeyCode::BackTab | KeyCode::Left => {
return Ok(WindowMode::Conversation(Some(
ConversationEvent::PromptRead,
)));
//return Ok(WindowMode::Modal(ModalEvent::Open(
// ModalWindowType::Files,
//)));
}
KeyCode::Right => {
return Ok(WindowMode::Modal(ModalEvent::Open(
ModalWindowType::ConversationList,
return Ok(WindowMode::Conversation(Some(
ConversationEvent::PromptRead,
)));
}
_ => {}
Expand Down
23 changes: 15 additions & 8 deletions lumni/src/apps/builtin/llm/prompt/src/tui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,30 @@ impl AppUi<'_> {
handler: &ConversationDbHandler,
) -> Result<(), ApplicationError> {
*window_mode = match key.code {
KeyCode::Left | KeyCode::BackTab => {
// TODO: handle workspace nav
KeyCode::BackTab => {
// TODO: move in main navigation
log::info!("BackTab");
return Ok(());
}
KeyCode::Left => {
self.modal = Some(Box::new(FileBrowserModal::new(None)));
WindowMode::Modal(ModalEvent::PollBackGroundTask)
}
KeyCode::Right | KeyCode::Tab => {
self.modal = Some(Box::new(
ConversationListModal::new(handler.clone()).await?,
));
WindowMode::Modal(ModalEvent::UpdateUI)
}
KeyCode::Up => {
self.modal = Some(Box::new(FileBrowserModal::new(None)));
WindowMode::Modal(ModalEvent::PollBackGroundTask)
// TODO: move up a block in conversation
log::info!("Shift Up");
return Ok(());
}
KeyCode::Down => {
// TODO: move down a block in conversation
log::info!("Shift Down");
return Ok(());
}
_ => {
return Ok(());
Expand Down Expand Up @@ -185,7 +196,3 @@ impl AppUi<'_> {
Ok(false)
}
}
enum TabDirection {
Left,
Right,
}
3 changes: 1 addition & 2 deletions lumni/src/apps/builtin/llm/prompt/src/tui/widgets/list.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Margin, Rect};
use ratatui::layout::{Margin, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::block::title;
use ratatui::widgets::{
Block, Borders, List, ListItem, ListState, Scrollbar, ScrollbarOrientation,
ScrollbarState, StatefulWidget, StatefulWidgetRef,
Expand Down
49 changes: 47 additions & 2 deletions lumni/src/apps/builtin/llm/prompt/src/tui/widgets/textarea.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::layout::{Margin, Rect};
use ratatui::style::Style;
use ratatui::widgets::{
Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, StatefulWidget,
Expand Down Expand Up @@ -71,6 +71,35 @@ impl<T: TextDocumentTrait> TextAreaWidget<T> {
pub fn new() -> Self {
Self(std::marker::PhantomData)
}

fn render_scrollbar(
&self,
buf: &mut Buffer,
area: Rect,
state: &TextAreaState<'_, T>,
) {
let scrollbar = Scrollbar::default()
.orientation(ScrollbarOrientation::VerticalRight)
.begin_symbol(None)
.end_symbol(None);

let scrollbar_area = area.inner(Margin {
vertical: 1,
horizontal: 0,
});

let total_lines = state.text_buffer.display_lines_len();
let viewport_height = area.height as usize;
let mut scrollbar_state =
ScrollbarState::new(total_lines).position(state.scroll_offset);

StatefulWidget::render(
scrollbar,
scrollbar_area,
buf,
&mut scrollbar_state,
);
}
}

impl<'a, T: TextDocumentTrait> TextAreaState<'a, T> {
Expand Down Expand Up @@ -143,12 +172,28 @@ impl<T: TextDocumentTrait> StatefulWidgetRef for &TextAreaWidget<T> {
state.text_buffer.set_width(area.width as usize);
state.text_buffer.update_display_text();

let total_lines = state.text_buffer.display_lines_len();
let viewport_height = area.height as usize;

// Adjust scroll if necessary
if total_lines > viewport_height {
state.scroll_offset =
state.scroll_offset.min(total_lines - viewport_height);
} else {
state.scroll_offset = 0;
}

let visible_text = state.text_buffer.display_window_lines(
state.scroll_offset,
state.scroll_offset + area.height as usize,
state.scroll_offset + viewport_height,
);

let paragraph = Paragraph::new(visible_text).style(Style::default());
Widget::render(paragraph, area, buf);

// Render scrollbar if there's more content than can fit in the viewport
if total_lines > viewport_height {
self.render_scrollbar(buf, area, state);
}
}
}

0 comments on commit 4dadeee

Please sign in to comment.