a2a_client/components/
task_viewer.rs1use a2a_rs::domain::{Task, part};
4use serde::Serialize;
5
6#[derive(Debug, Serialize, Clone)]
8pub struct TaskView {
9 pub task_id: String,
10 pub state: String,
11 pub message_count: usize,
12 pub last_message_preview: Option<String>,
13}
14
15impl TaskView {
16 pub fn from_task(task: Task) -> Self {
18 let message_count = task.history.len();
19 let last_message_preview = task.history.last().and_then(|msg| {
20 msg.parts.iter().find_map(|part| {
21 part.get_text()
22 .map(|text| text.chars().take(100).collect::<String>())
23 })
24 });
25
26 let state = match task.status.as_option().map(|s| &s.state) {
27 Some(::buffa::EnumValue::Known(state)) => match state {
28 a2a_rs::domain::TaskState::TASK_STATE_SUBMITTED => "Submitted".to_string(),
29 a2a_rs::domain::TaskState::TASK_STATE_WORKING => "Working".to_string(),
30 a2a_rs::domain::TaskState::TASK_STATE_INPUT_REQUIRED => "InputRequired".to_string(),
31 a2a_rs::domain::TaskState::TASK_STATE_COMPLETED => "Completed".to_string(),
32 a2a_rs::domain::TaskState::TASK_STATE_CANCELED => "Canceled".to_string(),
33 a2a_rs::domain::TaskState::TASK_STATE_FAILED => "Failed".to_string(),
34 a2a_rs::domain::TaskState::TASK_STATE_REJECTED => "Rejected".to_string(),
35 a2a_rs::domain::TaskState::TASK_STATE_AUTH_REQUIRED => "AuthRequired".to_string(),
36 a2a_rs::domain::TaskState::TASK_STATE_UNSPECIFIED => "Unknown".to_string(),
37 },
38 Some(::buffa::EnumValue::Unknown(num)) => format!("Unknown({})", num),
39 None => "Unknown".to_string(),
40 };
41
42 Self {
43 task_id: task.id,
44 state,
45 message_count,
46 last_message_preview,
47 }
48 }
49}
50
51#[derive(Debug, Serialize, Clone)]
53pub struct MessageView {
54 pub id: String,
55 pub role: String,
56 pub content: String,
57}
58
59impl MessageView {
60 pub fn from_message(msg: a2a_rs::domain::Message) -> Self {
62 let content = msg
64 .parts
65 .iter()
66 .map(|part| match &part.content {
67 Some(part::Content::Text(text)) => text.clone(),
68 Some(part::Content::Raw(_)) => format!(
69 "[File: {}]",
70 if part.filename.is_empty() {
71 "unnamed"
72 } else {
73 &part.filename
74 }
75 ),
76 Some(part::Content::Url(url)) => format!(
77 "[URL/File: {}]",
78 if part.filename.is_empty() {
79 url
80 } else {
81 &part.filename
82 }
83 ),
84 Some(part::Content::Data(data)) => {
85 let name = serde_json::to_value(&**data)
86 .ok()
87 .and_then(|v| {
88 v.get("name")
89 .and_then(|n| n.as_str())
90 .map(|s| s.to_string())
91 })
92 .unwrap_or_else(|| "unnamed".to_string());
93 format!("[Data: {}]", name)
94 }
95 None => String::new(),
96 })
97 .collect::<Vec<_>>()
98 .join("\n");
99
100 Self {
101 id: msg.message_id,
102 role: match &msg.role {
103 ::buffa::EnumValue::Known(a2a_rs::domain::Role::ROLE_USER) => "User".to_string(),
104 ::buffa::EnumValue::Known(a2a_rs::domain::Role::ROLE_AGENT) => "Agent".to_string(),
105 ::buffa::EnumValue::Known(r) => format!("{:?}", r),
106 ::buffa::EnumValue::Unknown(num) => format!("Unknown({})", num),
107 },
108 content,
109 }
110 }
111
112 pub fn from_message_with_json_parsing(msg: a2a_rs::domain::Message) -> Self {
114 let content = msg
115 .parts
116 .iter()
117 .filter_map(|part| part.get_text().map(|s| s.to_string()))
118 .collect::<Vec<_>>()
119 .join("\n");
120
121 let display_content =
123 if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&content) {
124 if let Some(obj) = json_value.as_object() {
125 match obj.get("type").and_then(|v| v.as_str()) {
126 Some("form") => obj
127 .get("instructions")
128 .and_then(|v| v.as_str())
129 .unwrap_or("Please fill out the form.")
130 .to_string(),
131 Some("result") => {
132 let message = obj
133 .get("message")
134 .and_then(|v| v.as_str())
135 .unwrap_or("Request processed.");
136 let status = obj
137 .get("status")
138 .and_then(|v| v.as_str())
139 .unwrap_or("unknown");
140 format!("{}\n\nStatus: {}", message, status)
141 }
142 _ => serde_json::to_string_pretty(&json_value).unwrap_or(content.clone()),
143 }
144 } else {
145 content.clone()
146 }
147 } else {
148 content.clone()
149 };
150
151 Self {
152 id: msg.message_id,
153 role: match &msg.role {
154 ::buffa::EnumValue::Known(a2a_rs::domain::Role::ROLE_USER) => "User".to_string(),
155 ::buffa::EnumValue::Known(a2a_rs::domain::Role::ROLE_AGENT) => "Agent".to_string(),
156 ::buffa::EnumValue::Known(r) => format!("{:?}", r),
157 ::buffa::EnumValue::Unknown(num) => format!("Unknown({})", num),
158 },
159 content: display_content,
160 }
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use a2a_rs::domain::{Message, Task, TaskState, TaskStatus};
168
169 #[test]
170 fn test_task_view_from_task() {
171 let mut task = Task::new("task-123".to_string(), "ctx-1".to_string());
172 task.status = ::buffa::MessageField::some(TaskStatus::new(TaskState::Working, None));
173
174 let msg = Message::user_text("A preview text here".to_string(), "msg-1".to_string());
176 task.history = vec![msg];
177
178 let view = TaskView::from_task(task);
179 assert_eq!(view.task_id, "task-123");
180 assert_eq!(view.state, "Working");
181 assert_eq!(view.message_count, 1);
182 assert_eq!(
183 view.last_message_preview,
184 Some("A preview text here".to_string())
185 );
186 }
187
188 #[test]
189 fn test_task_view_empty_history() {
190 let task = Task::new("task-123".to_string(), "ctx-1".to_string());
191 let view = TaskView::from_task(task);
192 assert_eq!(view.message_count, 0);
193 assert_eq!(view.last_message_preview, None);
194 }
195
196 #[test]
197 fn test_message_view_from_message() {
198 let msg = Message::user_text("Hello world".to_string(), "msg-1".to_string());
199 let view = MessageView::from_message(msg);
200
201 assert_eq!(view.id, "msg-1");
202 assert_eq!(view.role, "User");
203 assert_eq!(view.content, "Hello world");
204 }
205
206 #[test]
207 fn test_message_view_with_json_parsing_result() {
208 let json_content = r#"{"type":"result","message":"Success!","status":"ok"}"#;
209 let msg = Message::user_text(json_content.to_string(), "msg-2".to_string());
210
211 let view = MessageView::from_message_with_json_parsing(msg);
212 assert_eq!(view.content, "Success!\n\nStatus: ok");
213 }
214
215 #[test]
216 fn test_message_view_with_json_parsing_form() {
217 let json_content = r#"{"type":"form","instructions":"Please provide name"}"#;
218 let msg = Message::user_text(json_content.to_string(), "msg-3".to_string());
219
220 let view = MessageView::from_message_with_json_parsing(msg);
221 assert_eq!(view.content, "Please provide name");
222 }
223}