Add App core struct with event-handling and initialization logic for TUI.
This commit is contained in:
108
crates/owlen-cli/src/main.rs
Normal file
108
crates/owlen-cli/src/main.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
//! OWLEN CLI - Chat TUI client
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Arg, Command};
|
||||
use owlen_core::session::SessionController;
|
||||
use owlen_ollama::OllamaProvider;
|
||||
use owlen_tui::{config, ui, AppState, ChatApp, Event, EventHandler, SessionEvent};
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{backend::CrosstermBackend, Terminal};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let matches = Command::new("owlen")
|
||||
.about("OWLEN - A chat-focused TUI client for Ollama")
|
||||
.version("0.2.0")
|
||||
.arg(
|
||||
Arg::new("model")
|
||||
.short('m')
|
||||
.long("model")
|
||||
.value_name("MODEL")
|
||||
.help("Preferred model to use for this session"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let mut config = config::try_load_config().unwrap_or_default();
|
||||
|
||||
if let Some(model) = matches.get_one::<String>("model") {
|
||||
config.general.default_model = Some(model.clone());
|
||||
}
|
||||
|
||||
// Prepare provider from configuration
|
||||
let provider_cfg = config::ensure_ollama_config(&mut config).clone();
|
||||
let provider = Arc::new(OllamaProvider::from_config(
|
||||
&provider_cfg,
|
||||
Some(&config.general),
|
||||
)?);
|
||||
|
||||
let controller = SessionController::new(provider, config.clone());
|
||||
let (mut app, mut session_rx) = ChatApp::new(controller);
|
||||
app.initialize_models().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();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
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?;
|
||||
|
||||
// Persist configuration updates (e.g., selected model)
|
||||
config::save_config(app.config())?;
|
||||
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = result {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_app(
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
app: &mut ChatApp,
|
||||
mut event_rx: mpsc::UnboundedReceiver<Event>,
|
||||
session_rx: &mut mpsc::UnboundedReceiver<SessionEvent>,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
terminal.draw(|f| ui::render_chat(f, app))?;
|
||||
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user