1use crate::approval::types::{PermissionRequest, QuestionRequest};
12use crate::state::RecordingState;
13
14use super::VoiceApp;
15
16pub(crate) fn handle_sse_permission_asked(app: &mut VoiceApp, req: PermissionRequest) {
21 app.approval_queue.add_permission(req);
22 if app.state != RecordingState::ApprovalPending {
23 app.state = RecordingState::ApprovalPending;
24 }
25 app.render_display();
26}
27
28pub(crate) fn handle_sse_permission_replied(
34 app: &mut VoiceApp,
35 _session_id: &str,
36 request_id: &str,
37 _reply: &str,
38) {
39 app.approval_queue.remove(request_id);
40 if !app.approval_queue.has_pending() && app.state == RecordingState::ApprovalPending {
41 app.state = RecordingState::Idle;
42 }
43 app.render_display();
44}
45
46pub(crate) fn handle_sse_question_asked(app: &mut VoiceApp, req: QuestionRequest) {
51 app.approval_queue.add_question(req);
52 if app.state != RecordingState::ApprovalPending {
53 app.state = RecordingState::ApprovalPending;
54 }
55 app.render_display();
56}
57
58pub(crate) fn handle_sse_question_replied(
64 app: &mut VoiceApp,
65 _session_id: &str,
66 request_id: &str,
67 _answers: Vec<Vec<String>>,
68) {
69 app.approval_queue.remove(request_id);
70 if !app.approval_queue.has_pending() && app.state == RecordingState::ApprovalPending {
71 app.state = RecordingState::Idle;
72 }
73 app.render_display();
74}
75
76pub(crate) fn handle_sse_question_rejected(
82 app: &mut VoiceApp,
83 _session_id: &str,
84 request_id: &str,
85) {
86 app.approval_queue.remove(request_id);
87 if !app.approval_queue.has_pending() && app.state == RecordingState::ApprovalPending {
88 app.state = RecordingState::Idle;
89 }
90 app.render_display();
91}
92
93pub(crate) fn handle_sse_session_status(app: &mut VoiceApp, _session_id: &str, busy: bool) {
107 if busy && app.approval_queue.has_pending() {
108 app.display
110 .log("[voice] Session became busy — clearing pending approvals (answered externally).");
111 app.approval_queue.clear();
112 if app.state == RecordingState::ApprovalPending {
113 app.state = RecordingState::Idle;
114 }
115 app.render_display();
116 } else if !busy && app.approval_queue.has_pending() {
117 app.display
119 .log("[voice] Session idle — clearing stale approvals.");
120 app.approval_queue.clear();
121 if app.state == RecordingState::ApprovalPending {
122 app.state = RecordingState::Idle;
123 }
124 app.render_display();
125 }
126}
127
128pub(crate) fn refresh_approval_display(app: &mut VoiceApp) {
141 if app.approval_queue.has_pending() {
142 if app.state == RecordingState::Idle || app.state == RecordingState::ApprovalPending {
143 app.state = RecordingState::ApprovalPending;
144 }
145 } else if app.state == RecordingState::ApprovalPending {
146 app.state = RecordingState::Idle;
147 }
148 app.render_display();
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::app::VoiceApp;
155 use crate::approval::types::{PermissionRequest, QuestionRequest};
156 use crate::config::{AppConfig, ModelSize};
157 use std::path::PathBuf;
158
159 fn test_config() -> AppConfig {
160 AppConfig {
161 whisper_model_path: PathBuf::from("/nonexistent/model.bin"),
162 opencode_port: 4096,
163 toggle_key: ' ',
164 model_size: ModelSize::TinyEn,
165 auto_submit: true,
166 server_password: None,
167 data_dir: PathBuf::from("/nonexistent/data"),
168 audio_device: None,
169 use_global_hotkey: false,
170 global_hotkey: "right_option".to_string(),
171 push_to_talk: true,
172 approval_mode: true,
173 }
174 }
175
176 fn make_permission(id: &str) -> PermissionRequest {
177 PermissionRequest {
178 id: id.to_string(),
179 permission: "bash".to_string(),
180 metadata: serde_json::Value::Null,
181 }
182 }
183
184 fn make_question(id: &str) -> QuestionRequest {
185 QuestionRequest {
186 id: id.to_string(),
187 questions: vec![],
188 }
189 }
190
191 #[test]
192 fn test_permission_asked_transitions_to_approval_pending() {
193 let mut app = VoiceApp::new(test_config()).unwrap();
194 assert_eq!(app.state, RecordingState::Idle);
195 handle_sse_permission_asked(&mut app, make_permission("p1"));
196 assert_eq!(app.state, RecordingState::ApprovalPending);
197 assert!(app.approval_queue.has_pending());
198 }
199
200 #[test]
201 fn test_permission_replied_removes_from_queue_and_returns_to_idle() {
202 let mut app = VoiceApp::new(test_config()).unwrap();
203 handle_sse_permission_asked(&mut app, make_permission("p1"));
204 assert_eq!(app.state, RecordingState::ApprovalPending);
205
206 handle_sse_permission_replied(&mut app, "sess", "p1", "once");
207 assert_eq!(app.state, RecordingState::Idle);
208 assert!(!app.approval_queue.has_pending());
209 }
210
211 #[test]
212 fn test_question_asked_transitions_to_approval_pending() {
213 let mut app = VoiceApp::new(test_config()).unwrap();
214 handle_sse_question_asked(&mut app, make_question("q1"));
215 assert_eq!(app.state, RecordingState::ApprovalPending);
216 }
217
218 #[test]
219 fn test_question_replied_removes_from_queue() {
220 let mut app = VoiceApp::new(test_config()).unwrap();
221 handle_sse_question_asked(&mut app, make_question("q1"));
222 handle_sse_question_replied(&mut app, "sess", "q1", vec![]);
223 assert_eq!(app.state, RecordingState::Idle);
224 assert!(!app.approval_queue.has_pending());
225 }
226
227 #[test]
228 fn test_question_rejected_removes_from_queue() {
229 let mut app = VoiceApp::new(test_config()).unwrap();
230 handle_sse_question_asked(&mut app, make_question("q1"));
231 handle_sse_question_rejected(&mut app, "sess", "q1");
232 assert_eq!(app.state, RecordingState::Idle);
233 assert!(!app.approval_queue.has_pending());
234 }
235
236 #[test]
239 fn test_session_busy_clears_pending_approvals() {
240 let mut app = VoiceApp::new(test_config()).unwrap();
241 handle_sse_permission_asked(&mut app, make_permission("p1"));
242 handle_sse_question_asked(&mut app, make_question("q1"));
243 assert_eq!(app.state, RecordingState::ApprovalPending);
244 assert_eq!(app.approval_queue.len(), 2);
245
246 handle_sse_session_status(&mut app, "sess", true);
248 assert_eq!(app.state, RecordingState::Idle);
249 assert!(!app.approval_queue.has_pending());
250 }
251
252 #[test]
253 fn test_session_idle_clears_stale_approvals() {
254 let mut app = VoiceApp::new(test_config()).unwrap();
255 handle_sse_permission_asked(&mut app, make_permission("p1"));
256 assert_eq!(app.state, RecordingState::ApprovalPending);
257
258 handle_sse_session_status(&mut app, "sess", false);
260 assert_eq!(app.state, RecordingState::Idle);
261 assert!(!app.approval_queue.has_pending());
262 }
263
264 #[test]
265 fn test_session_busy_no_op_without_pending() {
266 let mut app = VoiceApp::new(test_config()).unwrap();
267 assert_eq!(app.state, RecordingState::Idle);
268
269 handle_sse_session_status(&mut app, "sess", true);
271 assert_eq!(app.state, RecordingState::Idle);
272 }
273
274 #[test]
275 fn test_session_busy_does_not_change_recording_state() {
276 let mut app = VoiceApp::new(test_config()).unwrap();
277 handle_sse_permission_asked(&mut app, make_permission("p1"));
278 app.state = RecordingState::Recording;
280
281 handle_sse_session_status(&mut app, "sess", true);
282 assert!(!app.approval_queue.has_pending());
284 assert_eq!(app.state, RecordingState::Recording);
285 }
286
287 #[test]
288 fn test_session_idle_no_op_without_pending() {
289 let mut app = VoiceApp::new(test_config()).unwrap();
290 assert_eq!(app.state, RecordingState::Idle);
291
292 handle_sse_session_status(&mut app, "sess", false);
294 assert_eq!(app.state, RecordingState::Idle);
295 assert!(!app.approval_queue.has_pending());
296 }
297
298 #[test]
299 fn test_session_idle_does_not_change_transcribing_state() {
300 let mut app = VoiceApp::new(test_config()).unwrap();
301 handle_sse_permission_asked(&mut app, make_permission("p1"));
302 app.state = RecordingState::Transcribing;
304
305 handle_sse_session_status(&mut app, "sess", false);
306 assert!(!app.approval_queue.has_pending());
308 assert_eq!(app.state, RecordingState::Transcribing);
309 }
310
311 #[test]
312 fn test_multiple_approvals_stay_pending_until_all_cleared() {
313 let mut app = VoiceApp::new(test_config()).unwrap();
314 handle_sse_permission_asked(&mut app, make_permission("p1"));
315 handle_sse_question_asked(&mut app, make_question("q1"));
316 assert_eq!(app.approval_queue.len(), 2);
317
318 handle_sse_permission_replied(&mut app, "sess", "p1", "once");
319 assert_eq!(app.state, RecordingState::ApprovalPending);
321
322 handle_sse_question_rejected(&mut app, "sess", "q1");
323 assert_eq!(app.state, RecordingState::Idle);
325 }
326}