pub mod actions;
pub mod activity;
pub mod app;
pub mod cards;
pub mod chat;
pub mod command_exec;
pub mod commands;
pub mod driver;
pub mod event_loop;
pub mod hit;
pub mod keymap;
pub mod llm_activity;
pub mod metrics;
pub mod modal;
pub mod modal_keys;
pub mod projection;
pub mod rail;
pub mod render;
pub mod theme;
pub mod uimsg;
pub mod wireshark;
use std::sync::Arc;
use anyhow::Result;
use tokio::sync::{mpsc, Mutex};
use crate::cli::theme::ColorPalette;
use crate::events::EventHandler;
use crate::llm::OllamaClient;
use crate::settings::Settings;
use crate::state::app_state::AppState;
use crate::ui::App;
use app::DashboardApp;
use event_loop::LoopContext;
use theme::Styles;
#[cfg(not(target_arch = "wasm32"))]
pub async fn run_dashboard(
state: AppState,
core: App,
event_handler: EventHandler,
llm_client: OllamaClient,
settings: Settings,
args: &crate::cli::Args,
palette: ColorPalette,
) -> Result<()> {
let (app, ctx) = prepare_dashboard(
state,
core,
event_handler,
llm_client,
settings,
args,
palette,
)
.await?;
event_loop::run(app, ctx).await
}
pub type DashboardReady =
Box<dyn FnOnce(mpsc::UnboundedSender<String>, mpsc::UnboundedSender<uimsg::UiMsg>)>;
#[allow(clippy::too_many_arguments)]
pub async fn run_dashboard_on<B, S>(
terminal: &mut ratatui::Terminal<B>,
events: S,
state: AppState,
core: App,
event_handler: EventHandler,
llm_client: OllamaClient,
settings: Settings,
args: &crate::cli::Args,
palette: ColorPalette,
on_ready: Option<DashboardReady>,
) -> Result<()>
where
B: ratatui::backend::Backend,
S: futures::Stream<Item = std::io::Result<crossterm::event::Event>> + Unpin,
{
let (app, ctx) = prepare_dashboard(
state,
core,
event_handler,
llm_client,
settings,
args,
palette,
)
.await?;
if let Some(ready) = on_ready {
ready(ctx.status_tx.clone(), app.ui_tx.clone());
}
event_loop::run_loop(terminal, events, app, ctx).await
}
async fn prepare_dashboard(
state: AppState,
mut core: App,
event_handler: EventHandler,
llm_client: OllamaClient,
settings: Settings,
args: &crate::cli::Args,
palette: ColorPalette,
) -> Result<(DashboardApp, LoopContext)> {
let settings = Arc::new(Mutex::new(settings));
let resolved = {
let guard = settings.lock().await;
crate::cli::model_select::resolve_startup_model(args, &guard).await?
};
state
.set_ollama_model(if resolved.model.is_empty() {
None
} else {
Some(resolved.model.clone())
})
.await;
core.connection_info.model = resolved.model.clone();
let web_search_mode = settings.lock().await.get_web_search_mode();
state.set_web_search_mode(web_search_mode).await;
if let Ok(Some(handler_mode)) = args.parse_event_handler_mode() {
state.set_event_handler_mode(handler_mode).await;
}
let (status_tx, status_rx) = mpsc::unbounded_channel::<String>();
let (web_approval_tx, web_approval_rx) = mpsc::unbounded_channel();
let (ui_tx, ui_rx) = mpsc::unbounded_channel::<uimsg::UiMsg>();
state.set_web_approval_channel(web_approval_tx).await;
let styles = Styles::from_palette(&palette);
let mut app = DashboardApp::new(core, styles, status_tx.clone(), ui_tx, llm_client.clone());
app.push_system("NetGet — a starts a server or client, Tab reaches them, F1 lists every key");
for message in resolved.messages {
app.push_system(message);
}
#[cfg(not(target_arch = "wasm32"))]
if args.show_art {
let base_url = resolved.base_url.clone();
let model = resolved.model.clone();
let tx = status_tx.clone();
tokio::spawn(async move {
let _ =
crate::cli::banner::generate_and_stream_ascii_banner(&base_url, &model, tx).await;
});
}
let ctx = LoopContext {
state,
event_handler,
llm_client,
settings,
status_tx,
status_rx,
web_approval_rx,
ui_rx,
};
Ok((app, ctx))
}