matrixcode_tui/workflow/
progress.rs1use ratatui::{
6 buffer::Buffer,
7 layout::Rect,
8 style::{Style, Color},
9};
10use crate::workflow::types::{WorkflowViewState, node_type_icon};
11
12pub fn render_progress_view(state: &WorkflowViewState, area: Rect, buf: &mut Buffer) {
14 if state.workflow_def.is_none() {
15 buf.set_string(area.x, area.y, "No workflow running", Style::default());
16 return;
17 }
18
19 let def = state.workflow_def.as_ref().unwrap();
20
21 let status_label = if let Some(ctx) = &state.context {
23 match ctx.status {
24 matrixcode_core::workflow::WorkflowStatus::Running => "⟳ running",
25 matrixcode_core::workflow::WorkflowStatus::Completed => "✓ completed",
26 matrixcode_core::workflow::WorkflowStatus::Failed => "✗ failed",
27 matrixcode_core::workflow::WorkflowStatus::Paused => "⏸ paused",
28 _ => "○ pending",
29 }
30 } else {
31 "○ pending"
32 };
33
34 let title = format!("{} [{}]", def.name, status_label);
35 let title_len = title.chars().count() as u16;
36 buf.set_string(area.x, area.y, &title, Style::default().fg(Color::White).bold());
37
38 if area.width > 30 {
40 let (completed, total) = state.progress();
41 let bar_start = area.x + title_len + 2;
42 let bar_width = area.width.saturating_sub(bar_start - area.x).saturating_sub(10);
43
44 if bar_width > 0 {
45 let filled = if total > 0 {
46 ((bar_width as usize) * completed) / total
47 } else {
48 0
49 };
50
51 buf.set_string(bar_start, area.y, " [", Style::default().fg(Color::Gray));
52 for i in 0..bar_width as usize {
53 let ch = if i < filled { "█" } else { "░" };
54 let color = if i < filled { Color::Green } else { Color::Gray };
55 buf.set_string(bar_start + 1 + i as u16, area.y, ch, Style::default().fg(color));
56 }
57 buf.set_string(bar_start + 1 + bar_width, area.y, "] ", Style::default().fg(Color::Gray));
58 buf.set_string(bar_start + 3 + bar_width, area.y, format!("{}/{}", completed, total), Style::default().fg(Color::Gray));
59 }
60 }
61
62 if area.height > 1 {
64 let node_y = area.y + 1;
65 let mut x = area.x;
66
67 buf.set_string(x, node_y, "Nodes: ", Style::default().fg(Color::Gray));
68 x += 7;
69
70 for node in &def.nodes {
71 if x >= area.right() {
72 break;
73 }
74 let status = state.get_node_status(&node.id);
75 let type_icon = node_type_icon(&node.node_type);
76 let status_icon = status.icon();
77
78 let color = match status.color() {
79 "gray" => Color::Gray,
80 "yellow" => Color::Yellow,
81 "green" => Color::Green,
82 "red" => Color::Red,
83 "blue" => Color::Blue,
84 _ => Color::Reset,
85 };
86
87 buf.set_string(x, node_y, format!("{}{}", type_icon, status_icon), Style::default().fg(color));
88 x += 3;
89 }
90 }
91}
92
93pub fn render_inline_progress(state: &WorkflowViewState, buf: &mut Buffer, x: u16, y: u16) -> u16 {
95 if state.workflow_def.is_none() {
96 return x;
97 }
98
99 let (completed, total) = state.progress();
100 let text = format!(" wf:{}/{:02}", completed, total);
101 let text_len = text.len() as u16;
102
103 let color = if completed == total && total > 0 {
104 Color::Green
105 } else if let Some(ctx) = &state.context {
106 match ctx.status {
107 matrixcode_core::workflow::WorkflowStatus::Failed => Color::Red,
108 matrixcode_core::workflow::WorkflowStatus::Running => Color::Yellow,
109 _ => Color::Gray,
110 }
111 } else {
112 Color::Gray
113 };
114
115 buf.set_string(x, y, &text, Style::default().fg(color));
116 x + text_len
117}