p2p_chat/ui/runner/
mod.rs1use super::{TerminalUI, UIAction, UIEvent};
3use crate::cli::commands::{Node, UiNotification};
4use crate::logging::{LogBuffer, TUILogCollector};
5use anyhow::Result;
6use crossterm::event::{self, Event};
7use std::sync::Arc;
8use std::time::Duration;
9use tokio::sync::mpsc;
10use tracing::{debug, error, info};
11
12mod actions;
13use actions::handle_ui_action;
14
15pub async fn run_tui(
31 node: Arc<Node>,
32 mut ui_notify_rx: tokio::sync::mpsc::UnboundedReceiver<UiNotification>,
33 web_port: u16,
34) -> Result<()> {
35 info!("🚀 Starting P2P Messenger TUI");
36
37 let log_buffer = Arc::new(LogBuffer::new(10000));
39
40 if let Err(e) = TUILogCollector::init_subscriber(log_buffer.clone()) {
42 debug!("Failed to initialize TUI log collector: {}", e);
43 }
44
45 let (ui_event_tx, ui_event_rx) = mpsc::unbounded_channel::<UIEvent>();
47 let (ui_action_tx, mut ui_action_rx) = mpsc::unbounded_channel::<UIAction>();
48
49 log_buffer.set_ui_sender(ui_event_tx.clone());
51
52 let _ = ui_event_tx.send(UIEvent::ChatMessage(format!(
54 "🌐 Web UI available at: http://127.0.0.1:{}",
55 web_port
56 )));
57
58 let mut terminal_ui = TerminalUI::new(ui_event_rx, ui_action_tx.clone());
60 terminal_ui.set_node(node.clone());
61 terminal_ui.set_log_buffer(log_buffer.clone());
62
63 const INITIAL_HISTORY_LIMIT: usize = 10;
64 if let Ok(initial_messages) = node
65 .history
66 .get_recent_messages(&node.identity.peer_id, INITIAL_HISTORY_LIMIT)
67 .await
68 {
69 terminal_ui.preload_messages(initial_messages);
70 }
71
72 let friends = match node.friends.list_friends().await {
74 Ok(friends_list) => friends_list
75 .into_iter()
76 .filter_map(|f| f.nickname.or_else(|| Some(f.peer_id.to_string())))
77 .collect(),
78 Err(e) => {
79 debug!("Failed to load friends for autocompletion: {}", e);
80 Vec::new()
81 }
82 };
83
84 terminal_ui.update_friends(friends);
85
86 let ui_event_tx_clone = ui_event_tx.clone();
88 tokio::spawn(async move {
89 loop {
90 if event::poll(Duration::from_millis(100)).unwrap_or(false) {
91 match event::read() {
92 Ok(Event::Key(key_event)) => {
93 if let Err(e) = ui_event_tx_clone.send(UIEvent::KeyPress(key_event)) {
94 debug!("Failed to send key event: {}", e);
95 break;
96 }
97 }
98 Ok(Event::Resize(width, height)) => {
99 if let Err(e) = ui_event_tx_clone.send(UIEvent::Resize(width, height)) {
100 debug!("Failed to send resize event: {}", e);
101 break;
102 }
103 }
104 _ => {}
105 }
106 }
107 }
108 });
109
110 let node_clone = node.clone();
112 let ui_event_tx_actions = ui_event_tx.clone();
113 tokio::spawn(async move {
114 while let Some(action) = ui_action_rx.recv().await {
115 if let Err(e) = handle_ui_action(action, &node_clone, ui_event_tx_actions.clone()).await
116 {
117 error!("Failed to dispatch UI action: {}", e);
118 }
119 }
120 });
121
122 let ui_event_tx_notifications = ui_event_tx.clone();
124 let node_for_notifications = node.clone();
125 tokio::spawn(async move {
126 while let Some(notification) = ui_notify_rx.recv().await {
127 match notification {
128 UiNotification::NewMessage(message) => {
129 if let Err(e) = ui_event_tx_notifications.send(UIEvent::NewMessage(message)) {
130 debug!("Failed to send new message event: {}", e);
131 break;
132 }
133 }
134 UiNotification::PeerConnected(_) | UiNotification::PeerDisconnected(_) => {
135 if let Ok(peers) = node_for_notifications.network.get_connected_peers().await {
137 let _ = ui_event_tx_notifications.send(UIEvent::UpdatePeersCount(peers.len()));
138 let peer_strings: Vec<String> =
139 peers.iter().map(|p| p.to_string()).collect();
140 let _ =
141 ui_event_tx_notifications.send(UIEvent::UpdateDiscoveredPeers(peer_strings));
142 }
143 }
144 UiNotification::DeliveryStatusUpdate { .. } => {
145 }
147 }
148 }
149 });
150
151 let ui_event_tx_peers = ui_event_tx.clone();
153 let node_peers = node.clone();
154 tokio::spawn(async move {
155 let mut interval = tokio::time::interval(std::time::Duration::from_secs(30));
156 loop {
157 interval.tick().await;
158 match node_peers.network.get_connected_peers().await {
159 Ok(peers) => {
160 let _ = ui_event_tx_peers.send(UIEvent::UpdatePeersCount(peers.len()));
161 let peer_strings: Vec<String> = peers.iter().map(|p| p.to_string()).collect();
162 let _ = ui_event_tx_peers.send(UIEvent::UpdateDiscoveredPeers(peer_strings));
163 }
164 Err(_) => {
165 }
167 }
168 }
169 });
170
171 let friends = match node.friends.list_friends().await {
173 Ok(friends_list) => friends_list
174 .into_iter()
175 .filter_map(|f| f.nickname.or_else(|| Some(f.peer_id.to_string())))
176 .collect(),
177 Err(e) => {
178 debug!("Failed to load friends for autocompletion: {}", e);
179 Vec::new()
180 }
181 };
182
183 info!(
184 "TUI initialized with {} friends for autocompletion",
185 friends.len()
186 );
187
188 match node.network.get_connected_peers().await {
190 Ok(peers) => {
191 let _ = ui_event_tx.send(UIEvent::UpdatePeersCount(peers.len()));
192 let peer_strings: Vec<String> = peers.iter().map(|p| p.to_string()).collect();
193 let _ = ui_event_tx.send(UIEvent::UpdateDiscoveredPeers(peer_strings));
194 }
195 Err(_) => {
196 }
198 }
199
200 terminal_ui.run().await
202}