1use tokio::sync::mpsc;
5
6use crate::event::{AgentEvent, AppEvent};
7
8use super::{App, ChatMessage, ConfirmState, ElicitationState, MessageRole, debug};
9
10impl App {
11 pub fn handle_event(&mut self, event: AppEvent) {
15 match event {
16 AppEvent::Key(key) => self.handle_key(key),
17 AppEvent::Tick => {
18 self.throbber_state.calc_next();
19 }
20 AppEvent::Resize(_, _) => {
21 self.sessions.current_mut().render_cache.clear();
22 }
23 AppEvent::MouseScroll(delta) => {
24 if self.confirm_state.is_none() {
25 if delta > 0 {
26 self.sessions.current_mut().scroll_offset =
27 self.sessions.current().scroll_offset.saturating_add(1);
28 } else {
29 self.sessions.current_mut().scroll_offset =
30 self.sessions.current().scroll_offset.saturating_sub(1);
31 }
32 }
33 }
34 AppEvent::Agent(agent_event) => self.handle_agent_event(agent_event),
35 AppEvent::Paste(text) => self.handle_paste(&text),
36 }
37 }
38
39 pub fn poll_agent_event(&mut self) -> impl Future<Output = Option<AgentEvent>> + use<'_> {
44 self.agent_event_rx.recv()
45 }
46
47 pub fn try_recv_agent_event(&mut self) -> Result<AgentEvent, mpsc::error::TryRecvError> {
57 self.agent_event_rx.try_recv()
58 }
59
60 #[allow(clippy::too_many_lines)] pub fn handle_agent_event(&mut self, event: AgentEvent) {
67 match event {
68 AgentEvent::Chunk(text) => {
69 self.sessions.current_mut().status_label = None;
70 if let Some(last) = self.sessions.current_mut().messages.last_mut()
71 && last.role == MessageRole::Assistant
72 && last.streaming
73 {
74 last.content.push_str(&text);
75 } else {
76 self.sessions
77 .current_mut()
78 .messages
79 .push(ChatMessage::new(MessageRole::Assistant, text).streaming());
80 self.trim_messages();
81 }
82 self.auto_scroll();
85 }
86 AgentEvent::FullMessage(text) => {
87 self.sessions.current_mut().status_label = None;
88 if !text.starts_with("[tool output") {
89 self.sessions
90 .current_mut()
91 .messages
92 .push(ChatMessage::new(MessageRole::Assistant, text));
93 self.trim_messages();
94 }
95 self.auto_scroll();
96 }
97 AgentEvent::Flush => {
98 if let Some(last) = self.sessions.current_mut().messages.last_mut()
99 && last.streaming
100 {
101 last.streaming = false;
102 let last_idx = self.sessions.current().messages.len().saturating_sub(1);
103 self.sessions
104 .current_mut()
105 .render_cache
106 .invalidate(last_idx);
107 }
108 }
109 AgentEvent::Typing => {
110 self.pending_count = self.pending_count.saturating_sub(1);
111 self.sessions.current_mut().status_label = Some("thinking...".to_owned());
112 }
113 AgentEvent::Status(text) => {
114 self.sessions.current_mut().status_label =
115 if text.is_empty() { None } else { Some(text) };
116 self.auto_scroll();
117 }
118 AgentEvent::ToolStart { tool_name, command } => {
119 self.sessions.current_mut().status_label = None;
120 self.sessions.current_mut().messages.push(
121 ChatMessage::new(MessageRole::Tool, format!("$ {command}\n"))
122 .streaming()
123 .with_tool(tool_name),
124 );
125 self.trim_messages();
126 self.auto_scroll();
127 }
128 AgentEvent::ToolOutputChunk { chunk, .. } => {
129 if let Some(pos) = self
130 .sessions
131 .current_mut()
132 .messages
133 .iter()
134 .rposition(|m| m.role == MessageRole::Tool && m.streaming)
135 {
136 self.sessions.current_mut().messages[pos]
137 .content
138 .push_str(&chunk);
139 self.sessions.current_mut().render_cache.invalidate(pos);
140 }
141 self.auto_scroll();
142 }
143 AgentEvent::ToolOutput {
144 tool_name,
145 output,
146 diff,
147 filter_stats,
148 kept_lines,
149 ..
150 } => {
151 self.handle_tool_output_event(tool_name, output, diff, filter_stats, kept_lines);
152 }
153 AgentEvent::ConfirmRequest {
154 prompt,
155 response_tx,
156 } => {
157 self.confirm_state = Some(ConfirmState {
158 prompt,
159 response_tx: Some(response_tx),
160 });
161 }
162 AgentEvent::ElicitationRequest {
163 request,
164 response_tx,
165 } => {
166 let dialog = crate::widgets::elicitation::ElicitationDialogState::new(request);
167 self.elicitation_state = Some(ElicitationState {
168 dialog,
169 response_tx: Some(response_tx),
170 });
171 }
172 AgentEvent::QueueCount(count) => {
173 self.queued_count = count;
174 self.pending_count = count;
175 }
176 AgentEvent::DiffReady(diff) => self.handle_diff_ready(diff),
177 AgentEvent::CommandResult { output, .. } => {
178 self.command_palette = None;
179 self.sessions
180 .current_mut()
181 .messages
182 .push(ChatMessage::new(MessageRole::System, output));
183 self.trim_messages();
184 self.auto_scroll();
185 }
186 AgentEvent::SetCancelSignal(signal) => {
187 self.set_cancel_signal(signal);
188 }
189 AgentEvent::SetMetricsRx(rx) => {
190 self.set_metrics_rx(rx);
191 }
192 }
193 }
194
195 fn handle_diff_ready(&mut self, diff: zeph_core::DiffData) {
196 if let Some(msg) = self
197 .sessions
198 .current_mut()
199 .messages
200 .iter_mut()
201 .rev()
202 .find(|m| m.role == MessageRole::Tool)
203 {
204 msg.diff_data = Some(diff);
205 }
206 }
207
208 fn handle_tool_output_event(
209 &mut self,
210 tool_name: zeph_common::ToolName,
211 output: String,
212 diff: Option<zeph_core::DiffData>,
213 filter_stats: Option<String>,
214 kept_lines: Option<Vec<usize>>,
215 ) {
216 debug!(
217 %tool_name,
218 has_diff = diff.is_some(),
219 has_filter_stats = filter_stats.is_some(),
220 output_len = output.len(),
221 "TUI ToolOutput event received"
222 );
223 if let Some(pos) = self
224 .sessions
225 .current_mut()
226 .messages
227 .iter()
228 .rposition(|m| m.role == MessageRole::Tool && m.streaming)
229 {
230 debug!("finalizing existing streaming Tool message");
236 let header_end = self.sessions.current_mut().messages[pos]
237 .content
238 .find('\n')
239 .map_or(0, |i| i + 1);
240 self.sessions.current_mut().messages[pos]
241 .content
242 .truncate(header_end);
243 self.sessions.current_mut().messages[pos]
244 .content
245 .push_str(&output);
246 self.sessions.current_mut().messages[pos].streaming = false;
247 self.sessions.current_mut().messages[pos].diff_data = diff;
248 self.sessions.current_mut().messages[pos].filter_stats = filter_stats;
249 self.sessions.current_mut().messages[pos].kept_lines = kept_lines;
250 self.sessions.current_mut().render_cache.invalidate(pos);
251 } else if diff.is_some() || filter_stats.is_some() || kept_lines.is_some() {
252 debug!("creating new Tool message with diff (no prior ToolStart)");
254 let mut msg = ChatMessage::new(MessageRole::Tool, output).with_tool(tool_name);
255 msg.diff_data = diff;
256 msg.filter_stats = filter_stats;
257 msg.kept_lines = kept_lines;
258 self.sessions.current_mut().messages.push(msg);
259 self.trim_messages();
260 } else if let Some(msg) = self
261 .sessions
262 .current_mut()
263 .messages
264 .iter_mut()
265 .rev()
266 .find(|m| m.role == MessageRole::Tool)
267 {
268 msg.filter_stats = filter_stats;
269 }
270 self.auto_scroll();
271 }
272
273 #[must_use]
274 pub fn confirm_state(&self) -> Option<&ConfirmState> {
275 self.confirm_state.as_ref()
276 }
277}