Split tui/mod.rs up into multiple files. (#3)

The mod.rs file was too large, split it up.

Reviewed-on: #3
Co-authored-by: Drew Galbraith <drew@tiramisu.one>
Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
Drew 2026-03-02 00:05:16 +00:00 committed by Drew
parent 3fd448d431
commit 6b85ff3cb8
4 changed files with 983 additions and 954 deletions

72
src/tui/events.rs Normal file
View file

@ -0,0 +1,72 @@
//! Server event draining: applies incoming [`UIEvent`]s to the TUI state.
use tokio::sync::mpsc;
use tracing::debug;
use super::AppState;
use crate::core::types::{Role, UIEvent};
/// Drain all pending [`UIEvent`]s from `event_rx` and apply them to `state`.
///
/// This is non-blocking: it processes all currently-available events and returns
/// immediately when the channel is empty.
///
/// | Event | Effect |
/// |--------------------|------------------------------------------------------------|
/// | `StreamDelta(s)` | Append `s` to last message if it's `Assistant`; else push new |
/// | `TurnComplete` | No structural change; logged at debug level |
/// | `Error(msg)` | Push `(Assistant, "[error] {msg}")` |
pub(super) fn drain_ui_events(event_rx: &mut mpsc::Receiver<UIEvent>, state: &mut AppState) {
while let Ok(event) = event_rx.try_recv() {
match event {
UIEvent::StreamDelta(chunk) => {
if let Some((Role::Assistant, content)) = state.messages.last_mut() {
content.push_str(&chunk);
} else {
state.messages.push((Role::Assistant, chunk));
}
}
UIEvent::TurnComplete => {
debug!("turn complete");
}
UIEvent::Error(msg) => {
state
.messages
.push((Role::Assistant, format!("[error] {msg}")));
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn drain_appends_to_existing_assistant() {
let (tx, mut rx) = tokio::sync::mpsc::channel(8);
let mut state = AppState::new();
state.messages.push((Role::Assistant, "hello".to_string()));
tx.send(UIEvent::StreamDelta(" world".to_string()))
.await
.unwrap();
drop(tx);
drain_ui_events(&mut rx, &mut state);
assert_eq!(state.messages.last().unwrap().1, "hello world");
}
#[tokio::test]
async fn drain_creates_assistant_on_user_last() {
let (tx, mut rx) = tokio::sync::mpsc::channel(8);
let mut state = AppState::new();
state.messages.push((Role::User, "hi".to_string()));
tx.send(UIEvent::StreamDelta("hello".to_string()))
.await
.unwrap();
drop(tx);
drain_ui_events(&mut rx, &mut state);
assert_eq!(state.messages.len(), 2);
assert_eq!(state.messages[1].0, Role::Assistant);
assert_eq!(state.messages[1].1, "hello");
}
}