1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum GestureType {
12 Tap,
14 DoubleTap,
16 Hold { duration_ms: u64 },
18 Slide { direction: SlideDirection },
20 Tilt { angle: f32 },
22 Rotate { degrees: f32 },
24 Shake { intensity: f32 },
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30pub enum SlideDirection {
31 Up,
32 Down,
33 Left,
34 Right,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct InteractionEvent {
40 pub interaction_type: InteractionType,
42 pub confidence: f32,
44 pub timestamp_ms: u64,
46 pub source: String,
48 pub metadata: Option<serde_json::Value>,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub enum InteractionType {
55 Gesture(GestureType),
57 Voice {
59 text: String,
60 language: Option<String>,
61 },
62 Hotkey { key: String, modifiers: Vec<String> },
64 Button {
66 button_id: u8,
67 press_type: ButtonPressType,
68 },
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73pub enum ButtonPressType {
74 Single,
75 Double,
76 Long,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct HapticFeedback {
82 pub pattern: HapticPattern,
84 pub intensity: f32,
86 pub duration_ms: u32,
88 pub repeat_count: u8,
90 pub repeat_delay_ms: u32,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub enum HapticPattern {
97 Click,
99 Pulse,
101 Ramp,
103 Heartbeat,
105 Notification,
107 Alert,
109 Success,
111 Processing,
113 Custom(u8),
115}
116
117#[derive(Debug, Clone, Default, Serialize, Deserialize)]
119pub struct InteractionContext {
120 pub agent_id: String,
122 pub current_interaction: Option<InteractionEvent>,
124 pub recent_interactions: Vec<InteractionEvent>,
126 pub suggested_haptic: Option<HapticFeedback>,
128 pub tool_hints: Vec<ToolHint>,
130 pub expects_voice_response: bool,
132 pub session_id: Option<String>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct ToolHint {
139 pub tool: String,
141 pub priority: f32,
143 pub reason: String,
145}
146
147impl InteractionEvent {
148 pub fn gesture(gesture: GestureType, source: &str, confidence: f32) -> Self {
150 Self {
151 interaction_type: InteractionType::Gesture(gesture),
152 confidence,
153 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
154 source: source.to_string(),
155 metadata: None,
156 }
157 }
158
159 pub fn voice(text: &str, source: &str, confidence: f32) -> Self {
161 Self {
162 interaction_type: InteractionType::Voice {
163 text: text.to_string(),
164 language: None,
165 },
166 confidence,
167 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
168 source: source.to_string(),
169 metadata: None,
170 }
171 }
172
173 pub fn hotkey(key: &str, modifiers: Vec<String>, source: &str) -> Self {
175 Self {
176 interaction_type: InteractionType::Hotkey {
177 key: key.to_string(),
178 modifiers,
179 },
180 confidence: 1.0,
181 timestamp_ms: chrono::Utc::now().timestamp_millis() as u64,
182 source: source.to_string(),
183 metadata: None,
184 }
185 }
186}
187
188impl HapticFeedback {
189 pub fn click() -> Self {
191 Self {
192 pattern: HapticPattern::Click,
193 intensity: 0.7,
194 duration_ms: 50,
195 repeat_count: 0,
196 repeat_delay_ms: 0,
197 }
198 }
199
200 pub fn notification() -> Self {
202 Self {
203 pattern: HapticPattern::Notification,
204 intensity: 0.8,
205 duration_ms: 200,
206 repeat_count: 0,
207 repeat_delay_ms: 0,
208 }
209 }
210
211 pub fn success() -> Self {
213 Self {
214 pattern: HapticPattern::Success,
215 intensity: 0.6,
216 duration_ms: 150,
217 repeat_count: 1,
218 repeat_delay_ms: 100,
219 }
220 }
221
222 pub fn alert() -> Self {
224 Self {
225 pattern: HapticPattern::Alert,
226 intensity: 1.0,
227 duration_ms: 300,
228 repeat_count: 2,
229 repeat_delay_ms: 150,
230 }
231 }
232
233 pub fn processing() -> Self {
235 Self {
236 pattern: HapticPattern::Processing,
237 intensity: 0.4,
238 duration_ms: 100,
239 repeat_count: 3,
240 repeat_delay_ms: 200,
241 }
242 }
243}
244
245impl InteractionContext {
246 pub fn new(agent_id: &str) -> Self {
248 Self {
249 agent_id: agent_id.to_string(),
250 ..Default::default()
251 }
252 }
253
254 pub fn with_interaction(mut self, event: InteractionEvent) -> Self {
256 self.tool_hints = derive_tool_hints(&event);
257 self.suggested_haptic = suggest_haptic_for_interaction(&event);
258 self.expects_voice_response =
259 matches!(event.interaction_type, InteractionType::Voice { .. });
260 self.current_interaction = Some(event);
261 self
262 }
263
264 pub fn push_history(&mut self, event: InteractionEvent) {
266 const MAX_HISTORY: usize = 10;
267 self.recent_interactions.push(event);
268 if self.recent_interactions.len() > MAX_HISTORY {
269 self.recent_interactions.remove(0);
270 }
271 }
272}
273
274fn derive_tool_hints(event: &InteractionEvent) -> Vec<ToolHint> {
276 let mut hints = Vec::new();
277
278 match &event.interaction_type {
279 InteractionType::Gesture(gesture) => match gesture {
280 GestureType::DoubleTap => {
281 hints.push(ToolHint {
282 tool: "quick_action".to_string(),
283 priority: 0.9,
284 reason: "Double tap suggests quick action intent".to_string(),
285 });
286 }
287 GestureType::Hold { duration_ms } if *duration_ms > 1000 => {
288 hints.push(ToolHint {
289 tool: "context_menu".to_string(),
290 priority: 0.8,
291 reason: "Long hold suggests context menu or detailed action".to_string(),
292 });
293 }
294 GestureType::Slide { direction } => {
295 let tool = match direction {
296 SlideDirection::Up => "scroll_up",
297 SlideDirection::Down => "scroll_down",
298 SlideDirection::Left => "navigate_back",
299 SlideDirection::Right => "navigate_forward",
300 };
301 hints.push(ToolHint {
302 tool: tool.to_string(),
303 priority: 0.7,
304 reason: format!("Slide {:?} gesture", direction),
305 });
306 }
307 GestureType::Shake { intensity } if *intensity > 0.5 => {
308 hints.push(ToolHint {
309 tool: "cancel".to_string(),
310 priority: 0.85,
311 reason: "Shake gesture suggests cancel/undo intent".to_string(),
312 });
313 }
314 _ => {}
315 },
316 InteractionType::Voice { text, .. } => {
317 hints.push(ToolHint {
319 tool: "voice_response".to_string(),
320 priority: 0.9,
321 reason: "Voice input expects voice output".to_string(),
322 });
323 if text.to_lowercase().contains("show") || text.to_lowercase().contains("display") {
324 hints.push(ToolHint {
325 tool: "visual_display".to_string(),
326 priority: 0.7,
327 reason: "Voice command requests visual output".to_string(),
328 });
329 }
330 }
331 InteractionType::Hotkey { key, modifiers } => {
332 if modifiers.contains(&"Ctrl".to_string()) || modifiers.contains(&"Cmd".to_string()) {
333 hints.push(ToolHint {
334 tool: "keyboard_shortcut".to_string(),
335 priority: 0.8,
336 reason: format!("Hotkey {} with modifiers", key),
337 });
338 }
339 }
340 InteractionType::Button { press_type, .. } => match press_type {
341 ButtonPressType::Long => {
342 hints.push(ToolHint {
343 tool: "voice_input".to_string(),
344 priority: 0.9,
345 reason: "Long button press activates voice input".to_string(),
346 });
347 }
348 ButtonPressType::Double => {
349 hints.push(ToolHint {
350 tool: "quick_action".to_string(),
351 priority: 0.8,
352 reason: "Double button press for quick action".to_string(),
353 });
354 }
355 _ => {}
356 },
357 }
358
359 hints
360}
361
362fn suggest_haptic_for_interaction(event: &InteractionEvent) -> Option<HapticFeedback> {
364 match &event.interaction_type {
365 InteractionType::Gesture(GestureType::Tap) => Some(HapticFeedback::click()),
366 InteractionType::Gesture(GestureType::DoubleTap) => Some(HapticFeedback {
367 pattern: HapticPattern::Click,
368 intensity: 0.8,
369 duration_ms: 40,
370 repeat_count: 1,
371 repeat_delay_ms: 50,
372 }),
373 InteractionType::Gesture(GestureType::Hold { .. }) => Some(HapticFeedback::notification()),
374 InteractionType::Voice { .. } => Some(HapticFeedback::processing()),
375 InteractionType::Button {
376 press_type: ButtonPressType::Long,
377 ..
378 } => Some(HapticFeedback::notification()),
379 _ => None,
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386
387 #[test]
388 fn test_gesture_event_creation() {
389 let event = InteractionEvent::gesture(GestureType::Tap, "ring", 0.95);
390 assert!(matches!(
391 event.interaction_type,
392 InteractionType::Gesture(GestureType::Tap)
393 ));
394 assert_eq!(event.source, "ring");
395 assert_eq!(event.confidence, 0.95);
396 }
397
398 #[test]
399 fn test_voice_event_creation() {
400 let event = InteractionEvent::voice("hello world", "microphone", 0.9);
401 if let InteractionType::Voice { text, .. } = &event.interaction_type {
402 assert_eq!(text, "hello world");
403 } else {
404 panic!("Expected Voice interaction type");
405 }
406 }
407
408 #[test]
409 fn test_interaction_context_with_hints() {
410 let event = InteractionEvent::gesture(GestureType::DoubleTap, "ring", 0.9);
411 let ctx = InteractionContext::new("test-agent").with_interaction(event);
412
413 assert!(!ctx.tool_hints.is_empty());
414 assert!(ctx.tool_hints.iter().any(|h| h.tool == "quick_action"));
415 }
416
417 #[test]
418 fn test_haptic_feedback_presets() {
419 let click = HapticFeedback::click();
420 assert_eq!(click.pattern, HapticPattern::Click);
421 assert_eq!(click.repeat_count, 0);
422
423 let alert = HapticFeedback::alert();
424 assert_eq!(alert.pattern, HapticPattern::Alert);
425 assert_eq!(alert.repeat_count, 2);
426 }
427}