Skip to content

Commit

Permalink
refactor responses from modal, add ability to run long background tas…
Browse files Browse the repository at this point in the history
…ks in modal, show processing spinner when creating new profiles
  • Loading branch information
aprxi committed Aug 16, 2024
1 parent 9068d70 commit 6d32a27
Show file tree
Hide file tree
Showing 18 changed files with 374 additions and 198 deletions.
4 changes: 1 addition & 3 deletions lumni/src/apps/builtin/llm/prompt/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ use tokio::signal;
use tokio::sync::Mutex;
use tokio::time::{timeout, Duration};

use super::chat::db::{
ConversationDatabase, EncryptionHandler, ModelServerName,
};
use super::chat::db::{ConversationDatabase, ModelServerName};
use super::chat::{
prompt_app, App, AssistantManager, ChatEvent, NewConversation,
PromptInstruction, ThreadedChatSession,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use super::connector::{DatabaseConnector, DatabaseOperationError};
use super::encryption::EncryptionHandler;
use crate::external as lumni;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct UserProfileDbHandler {
profile_name: Option<String>,
db: Arc<TokioMutex<DatabaseConnector>>,
Expand Down
5 changes: 3 additions & 2 deletions lumni/src/apps/builtin/llm/prompt/src/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ pub use super::error::{PromptError, PromptNotReadyReason};
use super::server::{CompletionResponse, ModelServer, ServerManager};
use super::tui::{
draw_ui, AppUi, ColorScheme, ColorSchemeType, CommandLineAction,
ConversationEvent, KeyEventHandler, ModalWindowType, PromptAction,
TextLine, TextSegment, TextWindowTrait, WindowEvent, WindowKind,
ConversationEvent, KeyEventHandler, ModalAction, ModalWindowType,
PromptAction, TextLine, TextSegment, TextWindowTrait, UserEvent,
WindowEvent, WindowKind,
};

// gets PERSONAS from the generated code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use tokio::time::{interval, Duration};
use super::chat_session_manager::ChatEvent;
use super::db::{ConversationDatabase, ConversationDbHandler};
use super::{
App, ColorScheme, CommandLineAction, ConversationEvent, KeyEventHandler,
ModalWindowType, PromptAction, PromptInstruction, TextWindowTrait,
WindowEvent, WindowKind,
App, CommandLineAction, ConversationEvent, KeyEventHandler, ModalAction,
PromptAction, PromptInstruction, TextWindowTrait, UserEvent, WindowEvent,
WindowKind,
};
pub use crate::external as lumni;

Expand All @@ -36,15 +36,13 @@ pub async fn prompt_app<B: Backend>(
loop {
tokio::select! {
_ = tick.tick().fuse() => {
handle_tick(
terminal,
handle_input_event(
&mut app,
&mut redraw_ui,
&mut current_mode,
&mut key_event_handler,
&keep_running,
&mut db_conn,
&color_scheme
).await?;
}
_ = async {
Expand Down Expand Up @@ -72,25 +70,34 @@ pub async fn prompt_app<B: Backend>(
if let Some(WindowEvent::Quit) = current_mode {
break;
}

// Handle processing state
if app.is_processing {
redraw_ui = true;

// Handle modal refresh if it's responsible for the processing state
if let Some(WindowEvent::Modal(_)) = current_mode {
current_mode =
Some(handle_modal_refresh(&mut app, &mut db_conn).await?);
}
}

if redraw_ui {
app.draw_ui(terminal).await?;
redraw_ui = false;
}
}
Ok(())
}

async fn handle_tick<B: Backend>(
terminal: &mut Terminal<B>,
async fn handle_input_event(
app: &mut App<'_>,
redraw_ui: &mut bool,
current_mode: &mut Option<WindowEvent>,
key_event_handler: &mut KeyEventHandler,
keep_running: &Arc<AtomicBool>,
db_conn: &mut Arc<ConversationDatabase>,
color_scheme: &ColorScheme,
) -> Result<(), ApplicationError> {
if *redraw_ui {
app.draw_ui(terminal).await?;
*redraw_ui = false;
}

if poll(Duration::from_millis(1))? {
let event = read()?;
match event {
Expand All @@ -102,7 +109,6 @@ async fn handle_tick<B: Backend>(
key_event,
keep_running,
db_conn,
color_scheme,
)
.await?;
}
Expand All @@ -125,13 +131,12 @@ async fn handle_key_event(
key_event: KeyEvent,
keep_running: &Arc<AtomicBool>,
db_conn: &mut Arc<ConversationDatabase>,
color_scheme: &ColorScheme,
) -> Result<(), ApplicationError> {
*current_mode = if let Some(mode) = current_mode.take() {
if let Some(mode) = current_mode.take() {
let mut conversation_handler = db_conn.get_conversation_handler(
app.get_conversation_id_for_active_session(),
);
key_event_handler
let new_window_event = key_event_handler
.process_key(
key_event,
&mut app.ui,
Expand All @@ -140,23 +145,75 @@ async fn handle_key_event(
keep_running.clone(),
&mut conversation_handler,
)
.await?
} else {
None
};
match current_mode.as_mut() {
Some(WindowEvent::Prompt(prompt_action)) => {
handle_prompt_action(app, prompt_action.clone(), color_scheme)
.await?;
*current_mode = Some(app.ui.set_prompt_window(false));
.await?;
let result =
handle_window_event(app, new_window_event, db_conn).await?;
*current_mode = Some(result);
}

Ok(())
}

async fn handle_modal_refresh(
app: &mut App<'_>,
db_conn: &mut Arc<ConversationDatabase>,
) -> Result<WindowEvent, ApplicationError> {
let modal = app
.ui
.modal
.as_mut()
.expect("Modal should exist when in Modal mode");
let refresh_result = modal.refresh().await?;
match refresh_result {
WindowEvent::Modal(ModalAction::Refresh) => {
// If the modal still needs refreshing, keep the processing state
app.is_processing = true;
Ok(WindowEvent::Modal(ModalAction::Refresh))
}
Some(WindowEvent::CommandLine(action)) => {
handle_command_line_action(app, action.clone());
other_event => {
// Handle the event returned by refresh
app.is_processing = false;
handle_window_event(app, other_event, db_conn).await
}
Some(WindowEvent::Modal(modal_window_type)) => {
handle_modal_window(app, modal_window_type, db_conn).await?;
}
}

async fn handle_window_event(
app: &mut App<'_>,
window_event: WindowEvent,
db_conn: &mut Arc<ConversationDatabase>,
) -> Result<WindowEvent, ApplicationError> {
match window_event {
WindowEvent::Prompt(ref prompt_action) => {
handle_prompt_action(app, prompt_action.clone()).await?;
Ok(app.ui.set_prompt_window(false))
}
Some(WindowEvent::PromptWindow(event)) => {
WindowEvent::CommandLine(ref action) => {
handle_command_line_action(app, action.clone());
Ok(window_event)
}
WindowEvent::Modal(ref action) => match action {
ModalAction::Refresh => {
app.is_processing = true;
Ok(window_event)
}
ModalAction::Open(ref modal_window_type) => {
app.ui
.set_new_modal(
modal_window_type.clone(),
db_conn,
app.get_conversation_id_for_active_session(),
)
.await?;
Ok(window_event)
}
ModalAction::Event(ref user_event) => {
handle_modal_user_event(app, user_event, db_conn).await?;
Ok(window_event)
}
_ => Ok(window_event),
},
WindowEvent::PromptWindow(ref event) => {
let mut conversation_handler = db_conn.get_conversation_handler(
app.get_conversation_id_for_active_session(),
);
Expand All @@ -166,10 +223,10 @@ async fn handle_key_event(
&mut conversation_handler,
)
.await?;
Ok(window_event)
}
_ => {}
_ => Ok(window_event),
}
Ok(())
}

fn handle_mouse_event(app: &mut App, mouse_event: MouseEvent) -> bool {
Expand All @@ -190,11 +247,11 @@ fn handle_mouse_event(app: &mut App, mouse_event: MouseEvent) -> bool {
async fn handle_prompt_action(
app: &mut App<'_>,
prompt_action: PromptAction,
color_scheme: &ColorScheme,
) -> Result<(), ApplicationError> {
let color_scheme = app.color_scheme.clone();
match prompt_action {
PromptAction::Write(prompt) => {
send_prompt(app, &prompt, color_scheme).await?;
send_prompt(app, &prompt).await?;
}
PromptAction::Stop => {
app.stop_active_chat_session().await?;
Expand Down Expand Up @@ -226,27 +283,19 @@ fn handle_command_line_action(
}
}

async fn handle_modal_window(
async fn handle_modal_user_event(
app: &mut App<'_>,
modal_window_type: &ModalWindowType,
db_conn: &mut Arc<ConversationDatabase>,
user_event: &UserEvent,
_db_conn: &mut Arc<ConversationDatabase>,
) -> Result<(), ApplicationError> {
// switch to, or stay in modal window
match modal_window_type {
ModalWindowType::ConversationList(Some(_)) => {
// reload chat on any conversation event
match user_event {
UserEvent::ReloadConversation => {
_ = app.reload_conversation().await;
return Ok(());
}
_ => {}
}

if app.ui.needs_modal_update(modal_window_type) {
let conversation_id = app.get_conversation_id_for_active_session();
app.ui
.set_new_modal(modal_window_type.clone(), db_conn, conversation_id)
.await?;
}
Ok(())
}

Expand Down Expand Up @@ -283,9 +332,9 @@ async fn handle_prompt_window_event(
async fn send_prompt<'a>(
app: &mut App<'a>,
prompt: &str,
color_scheme: &ColorScheme,
) -> Result<(), ApplicationError> {
// prompt should end with single newline
let color_scheme = app.color_scheme.clone();
let formatted_prompt = format!("{}\n", prompt.trim_end());
let result = app
.chat_manager
Expand Down
10 changes: 6 additions & 4 deletions lumni/src/apps/builtin/llm/prompt/src/chat/session/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ pub use threaded_chat_session::ThreadedChatSession;
use super::db::{ConversationDatabase, ConversationId};
use super::{
db, draw_ui, AppUi, ColorScheme, ColorSchemeType, CommandLineAction,
CompletionResponse, ConversationEvent, KeyEventHandler, ModalWindowType,
ModelServer, PromptAction, PromptError, PromptInstruction,
PromptNotReadyReason, ServerManager, TextWindowTrait, WindowEvent,
WindowKind,
CompletionResponse, ConversationEvent, KeyEventHandler, ModalAction,
ModalWindowType, ModelServer, PromptAction, PromptError, PromptInstruction,
PromptNotReadyReason, ServerManager, TextWindowTrait, UserEvent,
WindowEvent, WindowKind,
};
pub use crate::external as lumni;

pub struct App<'a> {
pub ui: AppUi<'a>,
pub chat_manager: ChatSessionManager,
pub color_scheme: ColorScheme,
pub is_processing: bool, // flag to indicate if the app is busy processing
}

impl App<'_> {
Expand Down Expand Up @@ -57,6 +58,7 @@ impl App<'_> {
ui,
chat_manager,
color_scheme,
is_processing: false,
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ use lumni::api::error::ApplicationError;
use super::key_event::KeyTrack;
use super::text_window_event::handle_text_window_event;
use super::{
AppUi, ModalWindowType, PromptAction, TextWindowTrait, WindowEvent,
AppUi, ModalAction, ModalWindowType, PromptAction, TextWindowTrait,
WindowEvent,
};
pub use crate::external as lumni;

pub fn handle_command_line_event(
app_ui: &mut AppUi,
key_track: &mut KeyTrack,
is_running: Arc<AtomicBool>,
) -> Result<Option<WindowEvent>, ApplicationError> {
) -> Result<WindowEvent, ApplicationError> {
let key_code = key_track.current_key().code;

match key_code {
Expand All @@ -25,37 +26,37 @@ pub fn handle_command_line_event(
app_ui.command_line.text_empty();
app_ui.command_line.set_status_inactive();

Ok(Some(app_ui.set_response_window()))
Ok(app_ui.set_response_window())
}
KeyCode::Enter => {
let command = app_ui.command_line.text_buffer().to_string();
app_ui.command_line.text_empty();
app_ui.command_line.set_status_inactive();
if command.starts_with(':') {
match command.trim_start_matches(':') {
"q" => return Ok(Some(WindowEvent::Quit)),
"q" => return Ok(WindowEvent::Quit),
"w" => {
let question = app_ui.prompt.text_buffer().to_string();
app_ui.prompt.text_empty();
return Ok(Some(WindowEvent::Prompt(
PromptAction::Write(question),
return Ok(WindowEvent::Prompt(PromptAction::Write(
question,
)));
}
"stop" => {
return Ok(Some(WindowEvent::Prompt(
PromptAction::Stop,
)));
return Ok(WindowEvent::Prompt(PromptAction::Stop));
}
_ => {} // command not recognized
}
}
Ok(Some(WindowEvent::PromptWindow(None)))
Ok(WindowEvent::PromptWindow(None))
}
KeyCode::Char(':') => {
// double-colon opens Modal (Config) window
app_ui.command_line.text_empty();
app_ui.command_line.set_status_inactive();
Ok(Some(WindowEvent::Modal(ModalWindowType::ProfileEdit)))
Ok(WindowEvent::Modal(ModalAction::Open(
ModalWindowType::ProfileEdit,
)))
}
_ => handle_text_window_event(
key_track,
Expand Down
Loading

0 comments on commit 6d32a27

Please sign in to comment.