Skip to content

Commit 40dcc0a

Browse files
committed
integrates viewend into main loop
1 parent 1009e57 commit 40dcc0a

File tree

12 files changed

+2465
-65
lines changed

12 files changed

+2465
-65
lines changed

app.log.2025-10-24

Lines changed: 2053 additions & 0 deletions
Large diffs are not rendered by default.

crates/chat-cli-ui/examples/inline_input.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::sync::Arc;
22

3+
use chat_cli_ui::conduit::get_event_conduits;
34
use chat_cli_ui::ui::config::Config;
45
use chat_cli_ui::ui::{
56
App,
@@ -61,9 +62,14 @@ fn main() -> Result<()> {
6162

6263
let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?;
6364

65+
let (view_end, input_receiver, control_end) = get_event_conduits();
66+
_ = input_receiver;
67+
_ = control_end;
68+
6469
let mut app = App {
6570
config: Config::default(),
6671
should_quit: false,
72+
view_end,
6773
components: {
6874
let mut components = Vec::<Box<dyn Component>>::new();
6975

crates/chat-cli-ui/src/conduit.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,25 @@ pub fn get_legacy_conduits(
622622
)
623623
}
624624

625+
pub fn get_event_conduits() -> (ViewEnd, InputReceiver, ControlEnd<DestinationStructuredOutput>) {
626+
let (state_tx, state_rx) = tokio::sync::mpsc::unbounded_channel::<Event>();
627+
let (input_tx, input_rx) = tokio::sync::mpsc::channel::<InputEvent>(10);
628+
629+
(
630+
ViewEnd {
631+
sender: input_tx,
632+
receiver: state_rx,
633+
},
634+
input_rx,
635+
ControlEnd {
636+
current_event: None,
637+
should_send_structured_event: true,
638+
sender: state_tx.clone(),
639+
pass_through_destination: PhantomData,
640+
},
641+
)
642+
}
643+
625644
pub trait InterimEvent {
626645
type Error: std::error::Error;
627646
fn insert_content(&mut self, content: &[u8]) -> Result<(), Self::Error>;

crates/chat-cli-ui/src/lib.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,39 @@
1+
use std::sync::Arc;
2+
3+
use ui::config::Config;
4+
use ui::{
5+
App,
6+
ChatWindow,
7+
Component,
8+
InputBar,
9+
};
10+
111
pub mod conduit;
212
pub mod legacy_ui_util;
313
pub mod protocol;
414
pub mod ui;
15+
16+
pub fn get_app(view_end: conduit::ViewEnd) -> App {
17+
App {
18+
config: Config::default(),
19+
should_quit: false,
20+
view_end,
21+
components: {
22+
let mut components = Vec::<Box<dyn Component>>::new();
23+
24+
// Add ChatWindow to display message content
25+
let chat_window = {
26+
let (width, height) = crossterm::terminal::size().expect("Failed to retrieve terminal size");
27+
let chat_window = ChatWindow::new(height, width);
28+
Box::new(chat_window)
29+
};
30+
components.push(chat_window);
31+
32+
// Add InputBar for user input
33+
let input_bar = Box::new(InputBar::default());
34+
components.push(input_bar);
35+
36+
Arc::new(tokio::sync::Mutex::new(components))
37+
},
38+
}
39+
}

crates/chat-cli-ui/src/protocol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ impl Event {
498498
}
499499
}
500500

501+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
501502
pub enum InputEvent {
502503
Text(String),
503504
Interrupt,

crates/chat-cli-ui/src/ui/action.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use serde::{
44
Serialize,
55
};
66

7+
use crate::protocol::InputEvent;
8+
79
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
810
pub enum Action {
911
Tick,
@@ -13,4 +15,5 @@ pub enum Action {
1315
ClearScreen,
1416
Error(String),
1517
Help,
18+
Input(InputEvent),
1619
}

crates/chat-cli-ui/src/ui/components/app.rs

Lines changed: 89 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use tokio::sync::mpsc::unbounded_channel;
77
use tracing::error;
88

99
use super::Component;
10+
use crate::conduit::ViewEnd;
1011
use crate::ui::action::Action;
1112
use crate::ui::config::{
1213
Config,
@@ -20,6 +21,7 @@ use crate::ui::tui::{
2021
pub struct App {
2122
pub config: Config,
2223
pub should_quit: bool,
24+
pub view_end: ViewEnd,
2325
pub components: Arc<Mutex<Vec<Box<dyn Component>>>>,
2426
}
2527

@@ -32,16 +34,37 @@ impl App {
3234
// TODO: make a defer routine that restores the terminal on exit
3335
tui.enter()?;
3436

35-
let mut event_receiver = tui.event_rx.take().expect("Missing event receiver");
37+
let mut terminal_event_receiver = tui.event_rx.take().expect("Missing event receiver");
3638
let components_clone = self.components.clone();
3739

3840
// Render Task
3941
tokio::spawn(async move {
4042
while render_rx.recv().await.is_some() {
4143
let mut components = components_clone.lock().await;
4244
tui.terminal.draw(|f| {
43-
for component in components.iter_mut() {
44-
if let Err(e) = component.draw(f, f.area()) {
45+
use ratatui::layout::{
46+
Constraint,
47+
Layout,
48+
};
49+
50+
// Split the screen: chat window takes most space, input bar at bottom
51+
let chunks = Layout::vertical([
52+
Constraint::Min(1), // Chat window takes remaining space
53+
Constraint::Length(3), // Input bar has fixed height of 3 lines
54+
])
55+
.split(f.area());
56+
57+
// Render each component in its designated area
58+
// First component (ChatWindow) gets the top area
59+
// Second component (InputBar) gets the bottom area
60+
for (i, component) in components.iter_mut().enumerate() {
61+
let rect = if i == 0 {
62+
chunks[0] // ChatWindow
63+
} else {
64+
chunks[1] // InputBar
65+
};
66+
67+
if let Err(e) = component.draw(f, rect) {
4568
error!("Error rendering component {:?}", e);
4669
}
4770
}
@@ -59,14 +82,12 @@ impl App {
5982
tokio::spawn(async move {
6083
let mut key_event_buf = Vec::<crossterm::event::KeyEvent>::new();
6184

62-
while let Some(event) = event_receiver.recv().await {
63-
let Ok(action) = handle_events(&event, &mut key_event_buf, &config) else {
64-
error!("Error covnerting tui events to action");
85+
while let Some(event) = terminal_event_receiver.recv().await {
86+
let Ok(action) = handle_ui_events(&event, &mut key_event_buf, &config) else {
87+
error!("Error converting tui events to action");
6588
continue;
6689
};
6790

68-
tracing::info!("action: {:?}", action);
69-
7091
match action {
7192
Some(action) => {
7293
if let Err(e) = action_tx_clone.send(action) {
@@ -79,7 +100,7 @@ impl App {
79100
let mut components = components_clone.lock().await;
80101

81102
for component in components.iter_mut() {
82-
match component.handle_events(event.clone()) {
103+
match component.handle_terminal_events(event.clone()) {
83104
Ok(action) => {
84105
if let Some(action) = action {
85106
if let Err(e) = action_tx_clone.send(action) {
@@ -97,42 +118,76 @@ impl App {
97118
}
98119
});
99120

100-
// Main loop
101-
while let Some(action) = action_rx.recv().await {
102-
match action {
103-
Action::Render => {
104-
if let Err(e) = render_tx.send(()) {
105-
error!("Error sending rendering message to rendering thread: {:?}", e);
121+
loop {
122+
tokio::select! {
123+
session_event = self.view_end.receiver.recv() => {
124+
let Some(session_event) = session_event else {
125+
break;
126+
};
127+
128+
let mut components = self.components.lock().await;
129+
for component in components.iter_mut() {
130+
match component.handle_session_events(session_event.clone()) {
131+
Ok(subsequent_action) => {
132+
if let Some(subsequent_action) = subsequent_action {
133+
if let Err(e) = action_tx.send(subsequent_action) {
134+
error!("Error sending subsequent action: {:?}", e);
135+
}
136+
}
137+
},
138+
Err(e) => error!("Error updating component: {:?}", e),
139+
}
106140
}
107141
},
108-
Action::Tick => {},
109-
Action::Resize(_, _) => {},
110-
Action::Quit => {},
111-
Action::ClearScreen => {},
112-
Action::Error(_) => {},
113-
Action::Help => {},
114-
}
142+
action = action_rx.recv() => {
143+
let Some(action) = action else {
144+
break;
145+
};
115146

116-
let mut components = self.components.lock().await;
117-
for component in components.iter_mut() {
118-
match component.update(action.clone()) {
119-
Ok(subsequent_action) => {
120-
if let Some(subsequent_action) = subsequent_action {
121-
if let Err(e) = action_tx.send(subsequent_action) {
122-
error!("Error sending subsequent action: {:?}", e);
147+
match &action {
148+
Action::Render => {
149+
if let Err(e) = render_tx.send(()) {
150+
error!("Error sending rendering message to rendering thread: {:?}", e);
151+
}
152+
},
153+
Action::Tick => {},
154+
Action::Resize(_, _) => {},
155+
Action::Quit => {},
156+
Action::ClearScreen => {},
157+
Action::Error(_) => {},
158+
Action::Help => {},
159+
Action::Input(input_event) => {
160+
if let Err(e) = self.view_end.sender.send(input_event.clone()).await {
161+
error!("Error sending input event to control end: {:?}", e);
123162
}
124163
}
125-
},
126-
Err(e) => error!("Error updating component: {:?}", e),
127-
}
164+
}
165+
166+
let mut components = self.components.lock().await;
167+
for component in components.iter_mut() {
168+
match component.update(action.clone()) {
169+
Ok(subsequent_action) => {
170+
if let Some(subsequent_action) = subsequent_action {
171+
if let Err(e) = action_tx.send(subsequent_action) {
172+
error!("Error sending subsequent action: {:?}", e);
173+
}
174+
}
175+
},
176+
Err(e) => error!("Error updating component: {:?}", e),
177+
}
178+
}
179+
},
180+
128181
}
129182
}
183+
// Main loop
130184

131185
Ok(())
132186
}
133187
}
134188

135-
fn handle_events(
189+
#[inline]
190+
fn handle_ui_events(
136191
event: &Event,
137192
key_event_buf: &mut Vec<crossterm::event::KeyEvent>,
138193
config: &Config,
@@ -175,7 +230,7 @@ fn handle_events(
175230
},
176231
}
177232
},
178-
_ | KeyEventKind::Repeat => Ok(None),
233+
KeyEventKind::Repeat => Ok(None),
179234
}
180235
},
181236
_ => Err(eyre::eyre!("Event not yet supported")),

0 commit comments

Comments
 (0)