1use super::types::*;
4use std::collections::HashMap;
5use std::sync::Arc;
6use tokio::sync::RwLock;
7
8#[derive(Debug, Clone)]
10pub enum ReportFormat {
11 Markdown,
13 PlainText,
15 Json,
17}
18
19#[derive(Debug, Clone)]
21pub struct ReportConfig {
22 pub default_format: ReportFormat,
24 pub include_statistics: bool,
26 pub include_details: bool,
28 pub max_history: usize,
30}
31
32impl Default for ReportConfig {
33 fn default() -> Self {
34 Self {
35 default_format: ReportFormat::Markdown,
36 include_statistics: true,
37 include_details: true,
38 max_history: 100,
39 }
40 }
41}
42
43pub struct Reporter {
45 config: ReportConfig,
47 history: Arc<RwLock<Vec<Report>>>,
49 counter: Arc<RwLock<u64>>,
51}
52
53impl Reporter {
54 pub fn new(config: ReportConfig) -> Self {
56 Self {
57 config,
58 history: Arc::new(RwLock::new(Vec::new())),
59 counter: Arc::new(RwLock::new(0)),
60 }
61 }
62
63 async fn generate_id(&self) -> String {
65 let mut counter = self.counter.write().await;
66 *counter += 1;
67 format!("report_{}", *counter)
68 }
69
70 async fn save_report(&self, report: Report) {
72 let mut history = self.history.write().await;
73 history.push(report);
74
75 while history.len() > self.config.max_history {
77 history.remove(0);
78 }
79 }
80
81 pub async fn generate_completion_report(
83 &self,
84 todo: &TodoItem,
85 result: &ExecutionResult,
86 ) -> Report {
87 let id = self.generate_id().await;
88 let now = std::time::SystemTime::now()
89 .duration_since(std::time::UNIX_EPOCH)
90 .unwrap_or_default()
91 .as_secs();
92
93 let content = match self.config.default_format {
94 ReportFormat::Markdown => self.format_completion_markdown(todo, result),
95 ReportFormat::PlainText => self.format_completion_plain(todo, result),
96 ReportFormat::Json => self.format_completion_json(todo, result),
97 };
98
99 let mut statistics = HashMap::new();
100 statistics.insert(
101 "execution_time_ms".to_string(),
102 serde_json::json!(result.execution_time_ms),
103 );
104 statistics.insert("success".to_string(), serde_json::json!(result.success));
105 statistics.insert(
106 "artifacts_count".to_string(),
107 serde_json::json!(result.artifacts.len()),
108 );
109
110 let report = Report {
111 id,
112 report_type: ReportType::TaskCompletion,
113 todo_ids: vec![todo.id.clone()],
114 content,
115 statistics,
116 created_at: now,
117 };
118
119 self.save_report(report.clone()).await;
120 report
121 }
122
123 fn format_completion_markdown(&self, todo: &TodoItem, result: &ExecutionResult) -> String {
124 let status = if result.success {
125 "✅ 成功"
126 } else {
127 "❌ 失败"
128 };
129
130 let mut content = format!(
131 "# 任务完成汇报\n\n\
132 ## 基本信息\n\
133 - **任务ID**: {}\n\
134 - **状态**: {}\n\
135 - **执行时间**: {}ms\n\n\
136 ## 任务描述\n\
137 {}\n\n\
138 ## 执行结果\n\
139 {}\n",
140 todo.id, status, result.execution_time_ms, todo.raw_idea, result.summary
141 );
142
143 if self.config.include_details && !result.details.is_empty() {
144 content.push_str("\n## 详细信息\n");
145 for (key, value) in &result.details {
146 content.push_str(&format!("- **{}**: {}\n", key, value));
147 }
148 }
149
150 if !result.artifacts.is_empty() {
151 content.push_str("\n## 产出物\n");
152 for artifact in &result.artifacts {
153 content.push_str(&format!(
154 "- {} ({})\n",
155 artifact.name, artifact.artifact_type
156 ));
157 }
158 }
159
160 if let Some(ref error) = result.error {
161 content.push_str(&format!("\n## 错误信息\n```\n{}\n```\n", error));
162 }
163
164 content
165 }
166
167 fn format_completion_plain(&self, todo: &TodoItem, result: &ExecutionResult) -> String {
168 let status = if result.success { "成功" } else { "失败" };
169
170 format!(
171 "任务完成汇报\n\
172 ==============\n\
173 任务ID: {}\n\
174 状态: {}\n\
175 执行时间: {}ms\n\
176 任务描述: {}\n\
177 执行结果: {}\n",
178 todo.id, status, result.execution_time_ms, todo.raw_idea, result.summary
179 )
180 }
181
182 fn format_completion_json(&self, todo: &TodoItem, result: &ExecutionResult) -> String {
183 let report = serde_json::json!({
184 "todo_id": todo.id,
185 "success": result.success,
186 "execution_time_ms": result.execution_time_ms,
187 "description": todo.raw_idea,
188 "summary": result.summary,
189 "details": result.details,
190 "artifacts": result.artifacts,
191 "error": result.error,
192 });
193
194 serde_json::to_string_pretty(&report).unwrap_or_default()
195 }
196
197 pub async fn generate_progress_report(
199 &self,
200 todos: &[TodoItem],
201 statistics: HashMap<String, serde_json::Value>,
202 ) -> Report {
203 let id = self.generate_id().await;
204 let now = std::time::SystemTime::now()
205 .duration_since(std::time::UNIX_EPOCH)
206 .unwrap_or_default()
207 .as_secs();
208
209 let todo_ids: Vec<String> = todos.iter().map(|t| t.id.clone()).collect();
210
211 let content = match self.config.default_format {
212 ReportFormat::Markdown => self.format_progress_markdown(todos, &statistics),
213 ReportFormat::PlainText => self.format_progress_plain(todos, &statistics),
214 ReportFormat::Json => self.format_progress_json(todos, &statistics),
215 };
216
217 let report = Report {
218 id,
219 report_type: ReportType::Progress,
220 todo_ids,
221 content,
222 statistics,
223 created_at: now,
224 };
225
226 self.save_report(report.clone()).await;
227 report
228 }
229
230 fn format_progress_markdown(
231 &self,
232 todos: &[TodoItem],
233 statistics: &HashMap<String, serde_json::Value>,
234 ) -> String {
235 let mut content = "# 进度汇报\n\n".to_string();
236
237 if self.config.include_statistics {
238 content.push_str("## 统计信息\n");
239 for (key, value) in statistics {
240 content.push_str(&format!("- **{}**: {}\n", key, value));
241 }
242 content.push('\n');
243 }
244
245 content.push_str("## 任务列表\n");
246 for todo in todos {
247 let status_icon = match todo.status {
248 TodoStatus::Completed => "✅",
249 TodoStatus::InProgress => "🔄",
250 TodoStatus::Pending => "⏳",
251 TodoStatus::Cancelled => "❌",
252 _ => "📋",
253 };
254 content.push_str(&format!(
255 "- {} [{}] {:?}: {}\n",
256 status_icon,
257 todo.id,
258 todo.priority,
259 todo.raw_idea.chars().take(50).collect::<String>()
260 ));
261 }
262
263 content
264 }
265
266 fn format_progress_plain(
267 &self,
268 todos: &[TodoItem],
269 statistics: &HashMap<String, serde_json::Value>,
270 ) -> String {
271 let mut content = "进度汇报\n========\n\n".to_string();
272
273 if self.config.include_statistics {
274 content.push_str("统计信息:\n");
275 for (key, value) in statistics {
276 content.push_str(&format!(" {}: {}\n", key, value));
277 }
278 content.push('\n');
279 }
280
281 content.push_str("任务列表:\n");
282 for todo in todos {
283 content.push_str(&format!(
284 " - [{}] {:?} {:?}: {}\n",
285 todo.id,
286 todo.status,
287 todo.priority,
288 todo.raw_idea.chars().take(50).collect::<String>()
289 ));
290 }
291
292 content
293 }
294
295 fn format_progress_json(
296 &self,
297 todos: &[TodoItem],
298 statistics: &HashMap<String, serde_json::Value>,
299 ) -> String {
300 let report = serde_json::json!({
301 "statistics": statistics,
302 "todos": todos.iter().map(|t| {
303 serde_json::json!({
304 "id": t.id,
305 "status": format!("{:?}", t.status),
306 "priority": format!("{:?}", t.priority),
307 "description": t.raw_idea,
308 })
309 }).collect::<Vec<_>>(),
310 });
311
312 serde_json::to_string_pretty(&report).unwrap_or_default()
313 }
314
315 pub async fn generate_daily_summary(&self, todos: &[TodoItem]) -> Report {
317 let id = self.generate_id().await;
318 let now = std::time::SystemTime::now()
319 .duration_since(std::time::UNIX_EPOCH)
320 .unwrap_or_default()
321 .as_secs();
322
323 let todo_ids: Vec<String> = todos.iter().map(|t| t.id.clone()).collect();
324
325 let total = todos.len();
327 let completed = todos
328 .iter()
329 .filter(|t| t.status == TodoStatus::Completed)
330 .count();
331 let in_progress = todos
332 .iter()
333 .filter(|t| t.status == TodoStatus::InProgress)
334 .count();
335 let pending = todos
336 .iter()
337 .filter(|t| t.status == TodoStatus::Pending)
338 .count();
339
340 let mut statistics = HashMap::new();
341 statistics.insert("total".to_string(), serde_json::json!(total));
342 statistics.insert("completed".to_string(), serde_json::json!(completed));
343 statistics.insert("in_progress".to_string(), serde_json::json!(in_progress));
344 statistics.insert("pending".to_string(), serde_json::json!(pending));
345
346 let content = format!(
347 "# 每日总结\n\n\
348 ## 概览\n\
349 - 总任务数: {}\n\
350 - 已完成: {}\n\
351 - 进行中: {}\n\
352 - 待处理: {}\n\
353 - 完成率: {:.1}%\n\n\
354 ## 今日完成\n{}\n\
355 ## 进行中\n{}\n",
356 total,
357 completed,
358 in_progress,
359 pending,
360 if total > 0 {
361 (completed as f64 / total as f64) * 100.0
362 } else {
363 0.0
364 },
365 todos
366 .iter()
367 .filter(|t| t.status == TodoStatus::Completed)
368 .map(|t| format!("- {}\n", t.raw_idea.chars().take(50).collect::<String>()))
369 .collect::<String>(),
370 todos
371 .iter()
372 .filter(|t| t.status == TodoStatus::InProgress)
373 .map(|t| format!("- {}\n", t.raw_idea.chars().take(50).collect::<String>()))
374 .collect::<String>(),
375 );
376
377 let report = Report {
378 id,
379 report_type: ReportType::DailySummary,
380 todo_ids,
381 content,
382 statistics,
383 created_at: now,
384 };
385
386 self.save_report(report.clone()).await;
387 report
388 }
389
390 pub async fn get_history(&self) -> Vec<Report> {
392 let history = self.history.read().await;
393 history.clone()
394 }
395
396 pub async fn get_by_type(&self, report_type: ReportType) -> Vec<Report> {
398 let history = self.history.read().await;
399 history
400 .iter()
401 .filter(|r| r.report_type == report_type)
402 .cloned()
403 .collect()
404 }
405
406 pub async fn get_recent(&self, count: usize) -> Vec<Report> {
408 let history = self.history.read().await;
409 history.iter().rev().take(count).cloned().collect()
410 }
411}
412
413impl Default for Reporter {
414 fn default() -> Self {
415 Self::new(ReportConfig::default())
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[tokio::test]
424 async fn test_generate_completion_report() {
425 let reporter = Reporter::new(ReportConfig::default());
426
427 let todo = TodoItem::new("todo_1", "Build API", TodoPriority::High);
428 let result = ExecutionResult {
429 success: true,
430 summary: "API built successfully".to_string(),
431 details: HashMap::new(),
432 artifacts: vec![],
433 execution_time_ms: 5000,
434 error: None,
435 };
436
437 let report = reporter.generate_completion_report(&todo, &result).await;
438
439 assert_eq!(report.report_type, ReportType::TaskCompletion);
440 assert!(report.content.contains("成功"));
441 assert!(report.content.contains("Build API"));
442 }
443
444 #[tokio::test]
445 async fn test_generate_daily_summary() {
446 let reporter = Reporter::new(ReportConfig::default());
447
448 let mut todos = vec![
449 TodoItem::new("todo_1", "Task 1", TodoPriority::High),
450 TodoItem::new("todo_2", "Task 2", TodoPriority::Medium),
451 ];
452 todos[0].status = TodoStatus::Completed;
453 todos[1].status = TodoStatus::InProgress;
454
455 let report = reporter.generate_daily_summary(&todos).await;
456
457 assert_eq!(report.report_type, ReportType::DailySummary);
458 assert!(report.content.contains("50.0%")); }
460}