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 handle_prompts: true,
173 debug: false,
174 }
175 }
176
177 fn make_permission(id: &str) -> PermissionRequest {
178 PermissionRequest {
179 id: id.to_string(),
180 permission: "bash".to_string(),
181 metadata: serde_json::Value::Null,
182 }
183 }
184
185 fn make_question(id: &str) -> QuestionRequest {
186 QuestionRequest {
187 id: id.to_string(),
188 questions: vec![],
189 }
190 }
191
192 #[test]
193 fn test_permission_asked_transitions_to_approval_pending() {
194 let mut app = VoiceApp::new(test_config()).unwrap();
195 assert_eq!(app.state, RecordingState::Idle);
196 handle_sse_permission_asked(&mut app, make_permission("p1"));
197 assert_eq!(app.state, RecordingState::ApprovalPending);
198 assert!(app.approval_queue.has_pending());
199 }
200
201 #[test]
202 fn test_permission_replied_removes_from_queue_and_returns_to_idle() {
203 let mut app = VoiceApp::new(test_config()).unwrap();
204 handle_sse_permission_asked(&mut app, make_permission("p1"));
205 assert_eq!(app.state, RecordingState::ApprovalPending);
206
207 handle_sse_permission_replied(&mut app, "sess", "p1", "once");
208 assert_eq!(app.state, RecordingState::Idle);
209 assert!(!app.approval_queue.has_pending());
210 }
211
212 #[test]
213 fn test_question_asked_transitions_to_approval_pending() {
214 let mut app = VoiceApp::new(test_config()).unwrap();
215 handle_sse_question_asked(&mut app, make_question("q1"));
216 assert_eq!(app.state, RecordingState::ApprovalPending);
217 }
218
219 #[test]
220 fn test_question_replied_removes_from_queue() {
221 let mut app = VoiceApp::new(test_config()).unwrap();
222 handle_sse_question_asked(&mut app, make_question("q1"));
223 handle_sse_question_replied(&mut app, "sess", "q1", vec![]);
224 assert_eq!(app.state, RecordingState::Idle);
225 assert!(!app.approval_queue.has_pending());
226 }
227
228 #[test]
229 fn test_question_rejected_removes_from_queue() {
230 let mut app = VoiceApp::new(test_config()).unwrap();
231 handle_sse_question_asked(&mut app, make_question("q1"));
232 handle_sse_question_rejected(&mut app, "sess", "q1");
233 assert_eq!(app.state, RecordingState::Idle);
234 assert!(!app.approval_queue.has_pending());
235 }
236
237 #[test]
240 fn test_session_busy_clears_pending_approvals() {
241 let mut app = VoiceApp::new(test_config()).unwrap();
242 handle_sse_permission_asked(&mut app, make_permission("p1"));
243 handle_sse_question_asked(&mut app, make_question("q1"));
244 assert_eq!(app.state, RecordingState::ApprovalPending);
245 assert_eq!(app.approval_queue.len(), 2);
246
247 handle_sse_session_status(&mut app, "sess", true);
249 assert_eq!(app.state, RecordingState::Idle);
250 assert!(!app.approval_queue.has_pending());
251 }
252
253 #[test]
254 fn test_session_idle_clears_stale_approvals() {
255 let mut app = VoiceApp::new(test_config()).unwrap();
256 handle_sse_permission_asked(&mut app, make_permission("p1"));
257 assert_eq!(app.state, RecordingState::ApprovalPending);
258
259 handle_sse_session_status(&mut app, "sess", false);
261 assert_eq!(app.state, RecordingState::Idle);
262 assert!(!app.approval_queue.has_pending());
263 }
264
265 #[test]
266 fn test_session_busy_no_op_without_pending() {
267 let mut app = VoiceApp::new(test_config()).unwrap();
268 assert_eq!(app.state, RecordingState::Idle);
269
270 handle_sse_session_status(&mut app, "sess", true);
272 assert_eq!(app.state, RecordingState::Idle);
273 }
274
275 #[test]
276 fn test_session_busy_does_not_change_recording_state() {
277 let mut app = VoiceApp::new(test_config()).unwrap();
278 handle_sse_permission_asked(&mut app, make_permission("p1"));
279 app.state = RecordingState::Recording;
281
282 handle_sse_session_status(&mut app, "sess", true);
283 assert!(!app.approval_queue.has_pending());
285 assert_eq!(app.state, RecordingState::Recording);
286 }
287
288 #[test]
289 fn test_session_idle_no_op_without_pending() {
290 let mut app = VoiceApp::new(test_config()).unwrap();
291 assert_eq!(app.state, RecordingState::Idle);
292
293 handle_sse_session_status(&mut app, "sess", false);
295 assert_eq!(app.state, RecordingState::Idle);
296 assert!(!app.approval_queue.has_pending());
297 }
298
299 #[test]
300 fn test_session_idle_does_not_change_transcribing_state() {
301 let mut app = VoiceApp::new(test_config()).unwrap();
302 handle_sse_permission_asked(&mut app, make_permission("p1"));
303 app.state = RecordingState::Transcribing;
305
306 handle_sse_session_status(&mut app, "sess", false);
307 assert!(!app.approval_queue.has_pending());
309 assert_eq!(app.state, RecordingState::Transcribing);
310 }
311
312 #[test]
313 fn test_multiple_approvals_stay_pending_until_all_cleared() {
314 let mut app = VoiceApp::new(test_config()).unwrap();
315 handle_sse_permission_asked(&mut app, make_permission("p1"));
316 handle_sse_question_asked(&mut app, make_question("q1"));
317 assert_eq!(app.approval_queue.len(), 2);
318
319 handle_sse_permission_replied(&mut app, "sess", "p1", "once");
320 assert_eq!(app.state, RecordingState::ApprovalPending);
322
323 handle_sse_question_rejected(&mut app, "sess", "q1");
324 assert_eq!(app.state, RecordingState::Idle);
326 }
327}