Skip to main content

aster/blueprint/
time_travel.rs

1//! 时光倒流系统
2//!
3//!
4//! 提供:
5//! 1. 检查点管理(创建、列出、删除)
6//! 2. 回滚到任意检查点
7//! 3. 分支执行(从检查点创建新分支)
8//! 4. 历史比较和差异查看
9
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13use uuid::Uuid;
14
15use super::task_tree_manager::TaskTreeManager;
16use super::types::*;
17
18// ============================================================================
19// 检查点信息
20// ============================================================================
21
22/// 检查点信息(用于展示)
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct CheckpointInfo {
25    pub id: String,
26    #[serde(rename = "type")]
27    pub checkpoint_type: CheckpointType,
28    pub name: String,
29    pub description: Option<String>,
30    pub timestamp: DateTime<Utc>,
31    pub task_id: Option<String>,
32    pub task_name: Option<String>,
33    pub task_path: Option<Vec<String>>,
34    pub status: String,
35    pub can_restore: bool,
36    pub has_code_changes: bool,
37    pub code_changes_count: usize,
38}
39
40/// 检查点类型
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "lowercase")]
43pub enum CheckpointType {
44    Task,
45    Global,
46}
47
48/// 时间线视图
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct TimelineView {
51    pub checkpoints: Vec<CheckpointInfo>,
52    pub current_position: Option<String>,
53    pub branches: Vec<BranchInfo>,
54}
55
56/// 分支信息
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct BranchInfo {
59    pub id: String,
60    pub name: String,
61    pub from_checkpoint: String,
62    pub created_at: DateTime<Utc>,
63    pub status: BranchStatus,
64}
65
66/// 分支状态
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "lowercase")]
69pub enum BranchStatus {
70    Active,
71    Merged,
72    Abandoned,
73}
74
75/// 差异信息
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct DiffInfo {
78    pub file_path: String,
79    #[serde(rename = "type")]
80    pub diff_type: DiffType,
81    pub before_content: Option<String>,
82    pub after_content: Option<String>,
83    pub additions: usize,
84    pub deletions: usize,
85}
86
87/// 差异类型
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(rename_all = "lowercase")]
90pub enum DiffType {
91    Added,
92    Modified,
93    Deleted,
94}
95
96/// 比较结果
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct CompareResult {
99    pub from_checkpoint: String,
100    pub to_checkpoint: String,
101    pub task_changes: Vec<TaskChange>,
102    pub code_changes: Vec<DiffInfo>,
103    /// 时间差(毫秒)
104    pub time_elapsed: i64,
105}
106
107/// 任务变更
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct TaskChange {
110    pub task_id: String,
111    pub task_name: String,
112    pub from_status: String,
113    pub to_status: String,
114    pub iterations: Option<u32>,
115}
116
117/// 检查点详情
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CheckpointDetails {
120    pub checkpoint: CheckpointInfo,
121    pub code_snapshots: Vec<CodeSnapshot>,
122    pub test_result: Option<TestResult>,
123}
124
125// ============================================================================
126// 时光倒流管理器
127// ============================================================================
128
129/// 时光倒流管理器
130pub struct TimeTravelManager {
131    branches: HashMap<String, BranchInfo>,
132    current_branch: String,
133}
134
135impl Default for TimeTravelManager {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl TimeTravelManager {
142    /// 创建新的时光倒流管理器
143    pub fn new() -> Self {
144        Self {
145            branches: HashMap::new(),
146            current_branch: "main".to_string(),
147        }
148    }
149
150    // ------------------------------------------------------------------------
151    // 检查点列表
152    // ------------------------------------------------------------------------
153
154    /// 获取所有检查点(按时间排序)
155    pub fn get_all_checkpoints(&self, tree: &TaskTree) -> Vec<CheckpointInfo> {
156        let mut checkpoints = Vec::new();
157
158        // 收集全局检查点
159        for gc in &tree.global_checkpoints {
160            checkpoints.push(CheckpointInfo {
161                id: gc.id.clone(),
162                checkpoint_type: CheckpointType::Global,
163                name: gc.name.clone(),
164                description: gc.description.clone(),
165                timestamp: gc.timestamp,
166                task_id: None,
167                task_name: None,
168                task_path: None,
169                status: "全局快照".to_string(),
170                can_restore: gc.can_restore,
171                has_code_changes: !gc.file_changes.is_empty(),
172                code_changes_count: gc.file_changes.len(),
173            });
174        }
175
176        // 收集任务检查点
177        self.collect_task_checkpoints(&tree.root, &mut checkpoints, Vec::new());
178
179        // 按时间倒序排序
180        checkpoints.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
181
182        checkpoints
183    }
184
185    /// 递归收集任务检查点
186    fn collect_task_checkpoints(
187        &self,
188        node: &TaskNode,
189        result: &mut Vec<CheckpointInfo>,
190        path: Vec<String>,
191    ) {
192        let mut current_path = path;
193        current_path.push(node.name.clone());
194
195        for cp in &node.checkpoints {
196            result.push(CheckpointInfo {
197                id: cp.id.clone(),
198                checkpoint_type: CheckpointType::Task,
199                name: cp.name.clone(),
200                description: cp.description.clone(),
201                timestamp: cp.timestamp,
202                task_id: Some(node.id.clone()),
203                task_name: Some(node.name.clone()),
204                task_path: Some(current_path.clone()),
205                status: format!("{:?}", cp.task_status),
206                can_restore: cp.can_restore,
207                has_code_changes: !cp.code_snapshot.is_empty(),
208                code_changes_count: cp.code_snapshot.len(),
209            });
210        }
211
212        for child in &node.children {
213            self.collect_task_checkpoints(child, result, current_path.clone());
214        }
215    }
216
217    /// 获取时间线视图
218    pub fn get_timeline_view(&self, tree: &TaskTree) -> TimelineView {
219        let checkpoints = self.get_all_checkpoints(tree);
220        let branches: Vec<BranchInfo> = self
221            .branches
222            .values()
223            .filter(|b| b.status == BranchStatus::Active)
224            .cloned()
225            .collect();
226
227        TimelineView {
228            current_position: checkpoints.first().map(|c| c.id.clone()),
229            checkpoints,
230            branches,
231        }
232    }
233
234    // ------------------------------------------------------------------------
235    // 检查点操作
236    // ------------------------------------------------------------------------
237
238    /// 创建手动检查点
239    pub async fn create_manual_checkpoint(
240        &self,
241        tree_manager: &mut TaskTreeManager,
242        tree_id: &str,
243        name: String,
244        description: Option<String>,
245        task_id: Option<&str>,
246    ) -> Result<CheckpointInfo, String> {
247        if let Some(tid) = task_id {
248            // 创建任务检查点
249            let checkpoint = tree_manager
250                .create_task_checkpoint(tree_id, tid, name.clone(), description.clone())
251                .await
252                .map_err(|e| e.to_string())?;
253
254            let tree = tree_manager
255                .get_task_tree(tree_id)
256                .await
257                .ok_or_else(|| format!("任务树 {} 不存在", tree_id))?;
258            let task = TaskTreeManager::find_task(&tree.root, tid);
259
260            Ok(CheckpointInfo {
261                id: checkpoint.id,
262                checkpoint_type: CheckpointType::Task,
263                name: checkpoint.name,
264                description: checkpoint.description,
265                timestamp: checkpoint.timestamp,
266                task_id: Some(tid.to_string()),
267                task_name: task.map(|t| t.name.clone()),
268                task_path: None,
269                status: format!("{:?}", checkpoint.task_status),
270                can_restore: checkpoint.can_restore,
271                has_code_changes: !checkpoint.code_snapshot.is_empty(),
272                code_changes_count: checkpoint.code_snapshot.len(),
273            })
274        } else {
275            // 创建全局检查点
276            let checkpoint = tree_manager
277                .create_global_checkpoint(tree_id, name.clone(), description.clone())
278                .await
279                .map_err(|e| e.to_string())?;
280
281            Ok(CheckpointInfo {
282                id: checkpoint.id,
283                checkpoint_type: CheckpointType::Global,
284                name: checkpoint.name,
285                description: checkpoint.description,
286                timestamp: checkpoint.timestamp,
287                task_id: None,
288                task_name: None,
289                task_path: None,
290                status: "全局快照".to_string(),
291                can_restore: checkpoint.can_restore,
292                has_code_changes: !checkpoint.file_changes.is_empty(),
293                code_changes_count: checkpoint.file_changes.len(),
294            })
295        }
296    }
297
298    /// 回滚到检查点
299    pub async fn rollback(
300        &self,
301        tree_manager: &mut TaskTreeManager,
302        tree_id: &str,
303        checkpoint_id: &str,
304    ) -> Result<(), String> {
305        let tree = tree_manager
306            .get_task_tree(tree_id)
307            .await
308            .ok_or_else(|| format!("任务树 {} 不存在", tree_id))?;
309
310        let checkpoints = self.get_all_checkpoints(&tree);
311        let checkpoint = checkpoints
312            .iter()
313            .find(|c| c.id == checkpoint_id)
314            .ok_or_else(|| format!("检查点 {} 不存在", checkpoint_id))?;
315
316        if !checkpoint.can_restore {
317            return Err(format!("检查点 {} 无法恢复", checkpoint_id));
318        }
319
320        match checkpoint.checkpoint_type {
321            CheckpointType::Global => {
322                tree_manager
323                    .rollback_to_global_checkpoint(tree_id, checkpoint_id)
324                    .await
325                    .map_err(|e| e.to_string())?;
326                Ok(())
327            }
328            CheckpointType::Task => {
329                let task_id = checkpoint
330                    .task_id
331                    .as_ref()
332                    .ok_or_else(|| "任务检查点缺少 task_id".to_string())?;
333                tree_manager
334                    .rollback_to_checkpoint(tree_id, task_id, checkpoint_id)
335                    .await
336                    .map_err(|e| e.to_string())?;
337                Ok(())
338            }
339        }
340    }
341
342    /// 预览回滚效果
343    pub fn preview_rollback(
344        &self,
345        tree: &TaskTree,
346        checkpoint_id: &str,
347    ) -> Result<CompareResult, String> {
348        let checkpoints = self.get_all_checkpoints(tree);
349        let _target = checkpoints
350            .iter()
351            .find(|c| c.id == checkpoint_id)
352            .ok_or_else(|| format!("检查点 {} 不存在", checkpoint_id))?;
353
354        let current = checkpoints
355            .first()
356            .ok_or_else(|| "没有当前检查点".to_string())?;
357
358        self.compare_checkpoints(tree, checkpoint_id, &current.id)
359    }
360
361    // ------------------------------------------------------------------------
362    // 分支管理
363    // ------------------------------------------------------------------------
364
365    /// 从检查点创建新分支
366    pub async fn create_branch(
367        &mut self,
368        tree_manager: &mut TaskTreeManager,
369        tree_id: &str,
370        checkpoint_id: &str,
371        branch_name: String,
372    ) -> Result<BranchInfo, String> {
373        let tree = tree_manager
374            .get_task_tree(tree_id)
375            .await
376            .ok_or_else(|| format!("任务树 {} 不存在", tree_id))?;
377
378        let checkpoints = self.get_all_checkpoints(&tree);
379        let _checkpoint = checkpoints
380            .iter()
381            .find(|c| c.id == checkpoint_id)
382            .ok_or_else(|| format!("检查点 {} 不存在", checkpoint_id))?;
383
384        let branch = BranchInfo {
385            id: Uuid::new_v4().to_string(),
386            name: branch_name,
387            from_checkpoint: checkpoint_id.to_string(),
388            created_at: Utc::now(),
389            status: BranchStatus::Active,
390        };
391
392        // 回滚到检查点
393        self.rollback(tree_manager, tree_id, checkpoint_id).await?;
394
395        self.branches.insert(branch.id.clone(), branch.clone());
396
397        Ok(branch)
398    }
399
400    /// 切换分支
401    pub fn switch_branch(&mut self, branch_id: &str) -> Result<(), String> {
402        if !self.branches.contains_key(branch_id) {
403            return Err(format!("分支 {} 不存在", branch_id));
404        }
405
406        self.current_branch = branch_id.to_string();
407        Ok(())
408    }
409
410    /// 获取当前分支
411    pub fn get_current_branch(&self) -> &str {
412        &self.current_branch
413    }
414
415    /// 获取所有分支
416    pub fn get_branches(&self) -> Vec<&BranchInfo> {
417        self.branches.values().collect()
418    }
419
420    // ------------------------------------------------------------------------
421    // 比较和差异
422    // ------------------------------------------------------------------------
423
424    /// 比较两个检查点
425    pub fn compare_checkpoints(
426        &self,
427        tree: &TaskTree,
428        from_checkpoint_id: &str,
429        to_checkpoint_id: &str,
430    ) -> Result<CompareResult, String> {
431        let checkpoints = self.get_all_checkpoints(tree);
432
433        let from = checkpoints
434            .iter()
435            .find(|c| c.id == from_checkpoint_id)
436            .ok_or_else(|| format!("检查点 {} 不存在", from_checkpoint_id))?;
437
438        let to = checkpoints
439            .iter()
440            .find(|c| c.id == to_checkpoint_id)
441            .ok_or_else(|| format!("检查点 {} 不存在", to_checkpoint_id))?;
442
443        let time_elapsed = to.timestamp.timestamp_millis() - from.timestamp.timestamp_millis();
444
445        // TODO: 实际实现需要比较两个快照的任务状态和代码内容
446        Ok(CompareResult {
447            from_checkpoint: from_checkpoint_id.to_string(),
448            to_checkpoint: to_checkpoint_id.to_string(),
449            task_changes: Vec::new(),
450            code_changes: Vec::new(),
451            time_elapsed,
452        })
453    }
454
455    /// 查看检查点详情
456    pub fn get_checkpoint_details(
457        &self,
458        tree: &TaskTree,
459        checkpoint_id: &str,
460    ) -> Option<CheckpointDetails> {
461        // 查找全局检查点
462        if let Some(gc) = tree
463            .global_checkpoints
464            .iter()
465            .find(|c| c.id == checkpoint_id)
466        {
467            return Some(CheckpointDetails {
468                checkpoint: CheckpointInfo {
469                    id: gc.id.clone(),
470                    checkpoint_type: CheckpointType::Global,
471                    name: gc.name.clone(),
472                    description: gc.description.clone(),
473                    timestamp: gc.timestamp,
474                    task_id: None,
475                    task_name: None,
476                    task_path: None,
477                    status: "全局快照".to_string(),
478                    can_restore: gc.can_restore,
479                    has_code_changes: !gc.file_changes.is_empty(),
480                    code_changes_count: gc.file_changes.len(),
481                },
482                code_snapshots: gc
483                    .file_changes
484                    .iter()
485                    .map(|fc| CodeSnapshot {
486                        file_path: fc.file_path.clone(),
487                        content: fc.new_content.clone().unwrap_or_default(),
488                        hash: String::new(),
489                    })
490                    .collect(),
491                test_result: None,
492            });
493        }
494
495        // 查找任务检查点
496        self.find_task_checkpoint(&tree.root, checkpoint_id)
497    }
498
499    /// 在任务树中查找检查点
500    fn find_task_checkpoint(
501        &self,
502        node: &TaskNode,
503        checkpoint_id: &str,
504    ) -> Option<CheckpointDetails> {
505        for cp in &node.checkpoints {
506            if cp.id == checkpoint_id {
507                return Some(CheckpointDetails {
508                    checkpoint: CheckpointInfo {
509                        id: cp.id.clone(),
510                        checkpoint_type: CheckpointType::Task,
511                        name: cp.name.clone(),
512                        description: cp.description.clone(),
513                        timestamp: cp.timestamp,
514                        task_id: Some(node.id.clone()),
515                        task_name: Some(node.name.clone()),
516                        task_path: None,
517                        status: format!("{:?}", cp.task_status),
518                        can_restore: cp.can_restore,
519                        has_code_changes: !cp.code_snapshot.is_empty(),
520                        code_changes_count: cp.code_snapshot.len(),
521                    },
522                    code_snapshots: cp.code_snapshot.clone(),
523                    test_result: cp.test_result.clone(),
524                });
525            }
526        }
527
528        for child in &node.children {
529            if let Some(details) = self.find_task_checkpoint(child, checkpoint_id) {
530                return Some(details);
531            }
532        }
533
534        None
535    }
536
537    // ------------------------------------------------------------------------
538    // 可视化辅助
539    // ------------------------------------------------------------------------
540
541    /// 生成检查点树形图(用于终端显示)
542    pub fn generate_checkpoint_tree(&self, tree: &TaskTree) -> String {
543        let checkpoints = self.get_all_checkpoints(tree);
544        let mut lines = Vec::new();
545
546        lines.push("检查点时间线".to_string());
547        lines.push("============".to_string());
548        lines.push(String::new());
549
550        for (i, cp) in checkpoints.iter().enumerate() {
551            let is_last = i == checkpoints.len() - 1;
552            let prefix = if is_last { "└── " } else { "├── " };
553            let type_icon = if cp.checkpoint_type == CheckpointType::Global {
554                "🌍"
555            } else {
556                "📌"
557            };
558            let status_icon = if cp.can_restore { "✅" } else { "⚠️" };
559
560            lines.push(format!(
561                "{}{} {} {}",
562                prefix, type_icon, cp.name, status_icon
563            ));
564
565            let indent = if is_last { "    " } else { "│   " };
566            lines.push(format!(
567                "{}📅 {}",
568                indent,
569                cp.timestamp.format("%Y-%m-%d %H:%M:%S")
570            ));
571
572            if let Some(ref task_name) = cp.task_name {
573                lines.push(format!("{}📁 {}", indent, task_name));
574            }
575
576            lines.push(format!("{}💾 {} 个文件变更", indent, cp.code_changes_count));
577            lines.push(indent.to_string());
578        }
579
580        lines.join("\n")
581    }
582
583    /// 生成时间线 ASCII 图
584    pub fn generate_timeline_ascii(&self, tree: &TaskTree) -> String {
585        let checkpoints = self.get_all_checkpoints(tree);
586        let mut lines = Vec::new();
587
588        lines.push(String::new());
589        lines.push("时间线 →".to_string());
590        lines.push(String::new());
591
592        // 绘制时间线
593        let mut timeline = "○".to_string();
594        for _ in 0..checkpoints.len().saturating_sub(1) {
595            timeline.push_str("───●");
596        }
597        timeline.push_str("───◉ (当前)");
598        lines.push(timeline);
599
600        // 绘制标签
601        let mut labels = String::new();
602        for cp in checkpoints.iter().rev() {
603            let short_name: String = cp.name.chars().take(10).collect();
604            let display_name = if cp.name.chars().count() > 10 {
605                format!("{}..", short_name)
606            } else {
607                short_name
608            };
609            labels.push_str(&format!("{:<15}", display_name));
610        }
611        lines.push(labels);
612
613        lines.join("\n")
614    }
615}
616
617#[cfg(test)]
618mod tests {
619    use super::*;
620
621    #[test]
622    fn test_time_travel_manager_creation() {
623        let manager = TimeTravelManager::new();
624        assert_eq!(manager.get_current_branch(), "main");
625        assert!(manager.get_branches().is_empty());
626    }
627
628    #[test]
629    fn test_checkpoint_type_serialization() {
630        let task_type = CheckpointType::Task;
631        let global_type = CheckpointType::Global;
632
633        let task_json = serde_json::to_string(&task_type).unwrap();
634        let global_json = serde_json::to_string(&global_type).unwrap();
635
636        assert_eq!(task_json, "\"task\"");
637        assert_eq!(global_json, "\"global\"");
638    }
639}