matrixcode_tui/workflow/
progress.rs1use crate::workflow::types::{WorkflowViewState, node_type_icon};
6use ratatui::{
7 buffer::Buffer,
8 layout::Rect,
9 style::{Color, Style},
10};
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(
37 area.x,
38 area.y,
39 &title,
40 Style::default().fg(Color::White).bold(),
41 );
42
43 if area.width > 30 {
45 let (completed, total) = state.progress();
46 let bar_start = area.x + title_len + 2;
47 let bar_width = area
48 .width
49 .saturating_sub(bar_start - area.x)
50 .saturating_sub(10);
51
52 if bar_width > 0 {
53 let filled = if total > 0 {
54 ((bar_width as usize) * completed) / total
55 } else {
56 0
57 };
58
59 buf.set_string(bar_start, area.y, " [", Style::default().fg(Color::Gray));
60 for i in 0..bar_width as usize {
61 let ch = if i < filled { "█" } else { "░" };
62 let color = if i < filled {
63 Color::Green
64 } else {
65 Color::Gray
66 };
67 buf.set_string(
68 bar_start + 1 + i as u16,
69 area.y,
70 ch,
71 Style::default().fg(color),
72 );
73 }
74 buf.set_string(
75 bar_start + 1 + bar_width,
76 area.y,
77 "] ",
78 Style::default().fg(Color::Gray),
79 );
80 buf.set_string(
81 bar_start + 3 + bar_width,
82 area.y,
83 format!("{}/{}", completed, total),
84 Style::default().fg(Color::Gray),
85 );
86 }
87 }
88
89 if area.height > 1 {
91 let node_y = area.y + 1;
92 let mut x = area.x;
93
94 buf.set_string(x, node_y, "Nodes: ", Style::default().fg(Color::Gray));
95 x += 7;
96
97 for node in &def.nodes {
98 if x >= area.right() {
99 break;
100 }
101 let status = state.get_node_status(&node.id);
102 let type_icon = node_type_icon(&node.node_type);
103 let status_icon = status.icon();
104
105 let color = match status.color() {
106 "gray" => Color::Gray,
107 "yellow" => Color::Yellow,
108 "green" => Color::Green,
109 "red" => Color::Red,
110 "blue" => Color::Blue,
111 _ => Color::Reset,
112 };
113
114 buf.set_string(
115 x,
116 node_y,
117 format!("{}{}", type_icon, status_icon),
118 Style::default().fg(color),
119 );
120 x += 3;
121 }
122 }
123}
124
125pub fn render_inline_progress(state: &WorkflowViewState, buf: &mut Buffer, x: u16, y: u16) -> u16 {
127 if state.workflow_def.is_none() {
128 return x;
129 }
130
131 let (completed, total) = state.progress();
132 let text = format!(" wf:{}/{:02}", completed, total);
133 let text_len = text.len() as u16;
134
135 let color = if completed == total && total > 0 {
136 Color::Green
137 } else if let Some(ctx) = &state.context {
138 match ctx.status {
139 matrixcode_core::workflow::WorkflowStatus::Failed => Color::Red,
140 matrixcode_core::workflow::WorkflowStatus::Running => Color::Yellow,
141 _ => Color::Gray,
142 }
143 } else {
144 Color::Gray
145 };
146
147 buf.set_string(x, y, &text, Style::default().fg(color));
148 x + text_len
149}