soothe_client/appkit/
classifier.rs1use serde_json::Value;
4
5use crate::intent_hints::DEFAULT_DELIVERABLE_PHASES;
6use crate::stream_terminal::{is_turn_end_custom_data, STREAM_END};
7
8#[derive(Debug, Clone)]
10pub struct EventClassifier {
11 pub deliverable_phases: Vec<String>,
13 pub treat_status_idle_as_complete: bool,
15}
16
17impl Default for EventClassifier {
18 fn default() -> Self {
19 Self {
20 deliverable_phases: DEFAULT_DELIVERABLE_PHASES
21 .iter()
22 .map(|s| (*s).to_string())
23 .collect(),
24 treat_status_idle_as_complete: false,
25 }
26 }
27}
28
29impl EventClassifier {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn is_deliverable(&self, mode: &str, data: &Value) -> bool {
37 if mode != "custom" {
38 return false;
39 }
40 let phase = data
41 .get("phase")
42 .or_else(|| data.get("type"))
43 .and_then(|v| v.as_str())
44 .unwrap_or("");
45 self.deliverable_phases.iter().any(|p| p == phase)
46 }
47
48 pub fn is_terminal(&self, mode: &str, data: &Value) -> bool {
50 mode == "custom" && is_turn_end_custom_data(data)
51 }
52
53 pub fn extract_text(&self, mode: &str, data: &Value) -> Option<String> {
55 if mode == "messages" {
56 if let Some(arr) = data.as_array() {
57 for item in arr {
58 if let Some(c) = item.get("content").and_then(|v| v.as_str()) {
59 if !c.is_empty() {
60 return Some(c.to_string());
61 }
62 }
63 if let Some(c) = item.get("text").and_then(|v| v.as_str()) {
64 if !c.is_empty() {
65 return Some(c.to_string());
66 }
67 }
68 }
69 }
70 if let Some(c) = data.get("content").and_then(|v| v.as_str()) {
71 return Some(c.to_string());
72 }
73 }
74 if mode == "custom" {
75 if data.get("type").and_then(|v| v.as_str()) == Some(STREAM_END) {
76 return None;
77 }
78 if let Some(c) = data
79 .get("content")
80 .or_else(|| data.get("text"))
81 .and_then(|v| v.as_str())
82 {
83 return Some(c.to_string());
84 }
85 }
86 None
87 }
88}