feat(tui): tambah dan pasang sidebar dashboard permanen

Sidebar kanan permanen (Workflow/Tasks/Usage) menggantikan panel todo
ad-hoc yang lama. Widget baca state yang sudah ada, tidak ada perubahan
skema AppStateRest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
asepharyana
2026-07-14 23:41:53 +07:00
co-authored by Claude Sonnet 5
parent aa2b6acb95
commit 31c01cdf1d
3 changed files with 287 additions and 35 deletions
+46 -1
View File
@@ -14,7 +14,7 @@ use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph, Wrap};
use ratatui::Frame;
use super::theme::Theme;
use crate::app::workflow::engine::AgentState;
use crate::app::workflow::engine::{AgentState, WorkflowAgent};
/// Icons for agent states.
fn state_icon(state: AgentState) -> &'static str {
@@ -208,4 +208,49 @@ fn build_session_lines(state: &crate::app::state::rest::AppStateRest) -> Vec<Lin
lines
}
/// Render the compact Workflow widget for the persistent sidebar: one
/// line per agent (icon + name), truncated to whatever fits with a
/// trailing "+N more" hint pointing at `/workflow` for the full view.
///
/// Flow: bordered `Block` titled "Workflow" → empty state if no agents →
/// else `split_for_display` caps the list to the inner height (minus one
/// row for the hint line, if needed) → one line per visible agent.
pub fn draw_workflow_widget(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
let block = Block::default()
.title(Span::styled(" Workflow ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD)))
.borders(Borders::ALL)
.border_style(Style::default().fg(Theme::BORDER));
let budget = (block.inner(area).height as usize).max(1);
let agents = &state.workflow_engine.agents;
let lines: Vec<Line> = if agents.is_empty() {
vec![Line::from(Span::styled(
" No workflow running.",
Style::default().fg(Theme::TEXT_DIM),
))]
} else {
let show_hint = agents.len() > budget;
let item_budget = if show_hint { budget.saturating_sub(1).max(1) } else { budget };
let (visible, hidden) = super::split_for_display(agents.as_slice(), item_budget);
let mut lines: Vec<Line> = visible.iter().map(workflow_agent_line).collect();
if show_hint {
lines.push(super::overflow_hint_line(hidden, "/workflow"));
}
lines
};
let paragraph = Paragraph::new(lines).block(block);
frame.render_widget(paragraph, area);
}
/// One compact line for a single agent: state icon + name, state-colored.
fn workflow_agent_line(agent: &WorkflowAgent) -> Line<'static> {
let color = state_color(agent.status.state);
let icon = state_icon(agent.status.state);
Line::from(vec![
Span::styled(format!(" {icon} "), Style::default().fg(color).add_modifier(Modifier::BOLD)),
Span::styled(agent.name.clone(), Style::default().fg(Theme::TEXT)),
])
}
use ratatui::style::Color;