feat(app): introduce UiRuntime trait and RuntimeApp run loop, add crossterm event conversion, refactor CLI to use RuntimeApp for unified UI handling
This commit is contained in:
@@ -16,19 +16,19 @@ use owlen_core::{
|
||||
config::{Config, McpMode},
|
||||
mcp::remote_client::RemoteMcpClient,
|
||||
mode::Mode,
|
||||
provider::ProviderManager,
|
||||
providers::OllamaProvider,
|
||||
session::SessionController,
|
||||
storage::StorageManager,
|
||||
types::{ChatRequest, ChatResponse, Message, ModelInfo},
|
||||
};
|
||||
use owlen_tui::tui_controller::{TuiController, TuiRequest};
|
||||
use owlen_tui::{AppState, ChatApp, Event, EventHandler, SessionEvent, config, ui};
|
||||
use owlen_tui::{ChatApp, SessionEvent, app::App as RuntimeApp, config, ui};
|
||||
use std::any::Any;
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crossterm::{
|
||||
event::{DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture},
|
||||
@@ -407,6 +407,8 @@ async fn main() -> Result<()> {
|
||||
|
||||
let controller =
|
||||
SessionController::new(provider, cfg, storage.clone(), tui_controller, false).await?;
|
||||
let provider_manager = Arc::new(ProviderManager::default());
|
||||
let mut runtime = RuntimeApp::new(provider_manager);
|
||||
let (mut app, mut session_rx) = ChatApp::new(controller).await?;
|
||||
app.initialize_models().await?;
|
||||
if let Some(notice) = offline_notice {
|
||||
@@ -417,12 +419,6 @@ async fn main() -> Result<()> {
|
||||
// Set the initial mode
|
||||
app.set_mode(initial_mode).await;
|
||||
|
||||
// Event infrastructure
|
||||
let cancellation_token = CancellationToken::new();
|
||||
let (event_tx, event_rx) = mpsc::unbounded_channel();
|
||||
let event_handler = EventHandler::new(event_tx, cancellation_token.clone());
|
||||
let event_handle = tokio::spawn(async move { event_handler.run().await });
|
||||
|
||||
// Terminal setup
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
@@ -435,11 +431,7 @@ async fn main() -> Result<()> {
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
let result = run_app(&mut terminal, &mut app, event_rx, &mut session_rx).await;
|
||||
|
||||
// Shutdown
|
||||
cancellation_token.cancel();
|
||||
event_handle.await?;
|
||||
let result = run_app(&mut terminal, &mut runtime, &mut app, &mut session_rx).await;
|
||||
|
||||
// Persist configuration updates (e.g., selected model)
|
||||
config::save_config(&app.config())?;
|
||||
@@ -462,58 +454,17 @@ async fn main() -> Result<()> {
|
||||
|
||||
async fn run_app(
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
runtime: &mut RuntimeApp,
|
||||
app: &mut ChatApp,
|
||||
mut event_rx: mpsc::UnboundedReceiver<Event>,
|
||||
session_rx: &mut mpsc::UnboundedReceiver<SessionEvent>,
|
||||
) -> Result<()> {
|
||||
let stream_draw_interval = tokio::time::Duration::from_millis(50);
|
||||
let idle_tick = tokio::time::Duration::from_millis(100);
|
||||
let mut last_draw = tokio::time::Instant::now() - stream_draw_interval;
|
||||
let mut render = |terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
state: &mut ChatApp|
|
||||
-> Result<()> {
|
||||
terminal.draw(|f| ui::render_chat(f, state))?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
loop {
|
||||
// Advance loading animation frame
|
||||
app.advance_loading_animation();
|
||||
|
||||
let streaming_active = app.streaming_count() > 0;
|
||||
let draw_due = if streaming_active {
|
||||
last_draw.elapsed() >= stream_draw_interval
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if draw_due {
|
||||
terminal.draw(|f| ui::render_chat(f, app))?;
|
||||
last_draw = tokio::time::Instant::now();
|
||||
}
|
||||
|
||||
// Process any pending LLM requests AFTER UI has been drawn
|
||||
if let Err(e) = app.process_pending_llm_request().await {
|
||||
eprintln!("Error processing LLM request: {}", e);
|
||||
}
|
||||
|
||||
// Process any pending tool executions AFTER UI has been drawn
|
||||
if let Err(e) = app.process_pending_tool_execution().await {
|
||||
eprintln!("Error processing tool execution: {}", e);
|
||||
}
|
||||
|
||||
let sleep_duration = if streaming_active {
|
||||
stream_draw_interval
|
||||
.checked_sub(last_draw.elapsed())
|
||||
.unwrap_or_else(|| tokio::time::Duration::from_millis(0))
|
||||
} else {
|
||||
idle_tick
|
||||
};
|
||||
|
||||
tokio::select! {
|
||||
Some(event) = event_rx.recv() => {
|
||||
if let AppState::Quit = app.handle_event(event).await? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Some(session_event) = session_rx.recv() => {
|
||||
app.handle_session_event(session_event).await?;
|
||||
}
|
||||
_ = tokio::time::sleep(sleep_duration) => {}
|
||||
}
|
||||
}
|
||||
runtime.run(terminal, app, session_rx, &mut render).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user