refactor: Remove workflow-related commands and overlays from the application
This commit is contained in:
@@ -81,9 +81,7 @@ pub enum Action {
|
||||
ModelList,
|
||||
AbortTurn,
|
||||
Compact,
|
||||
RunWorkflow {
|
||||
script: String,
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
/// Apply an `Action` to the application state.
|
||||
@@ -394,11 +392,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
state.workflow_engine.agents.clear();
|
||||
state.workflow_engine.findings.clear();
|
||||
}
|
||||
if message.to_lowercase().contains("complete")
|
||||
&& state.misc.overlay == Overlay::Workflow
|
||||
{
|
||||
state.misc.overlay = Overlay::None;
|
||||
}
|
||||
// popup removed, no overlay to reset
|
||||
state.push_toast(Toast {
|
||||
kind: ToastKind::Info,
|
||||
message: message.clone(),
|
||||
@@ -435,9 +429,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
crate::dto::chat::message::Role::System,
|
||||
format!("✓ {message}"),
|
||||
));
|
||||
if state.misc.overlay == Overlay::Workflow {
|
||||
state.misc.overlay = Overlay::None;
|
||||
}
|
||||
// overlay removed
|
||||
state.dirty = true;
|
||||
} else if kind == "workflow_error" {
|
||||
state.push_toast(Toast {
|
||||
@@ -450,9 +442,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
crate::dto::chat::message::Role::System,
|
||||
format!("✗ {message}"),
|
||||
));
|
||||
if state.misc.overlay == Overlay::Workflow {
|
||||
state.misc.overlay = Overlay::None;
|
||||
}
|
||||
// overlay removed
|
||||
state.dirty = true;
|
||||
} else {
|
||||
state.push_toast(Toast::new(ToastKind::Info, message));
|
||||
@@ -537,18 +527,14 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
status,
|
||||
});
|
||||
}
|
||||
if state.misc.overlay != Overlay::Workflow {
|
||||
state.misc.overlay = Overlay::Workflow;
|
||||
}
|
||||
// popup removed
|
||||
state.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if turn_finished {
|
||||
maybe_trigger_review(state);
|
||||
if state.misc.overlay == Overlay::Workflow {
|
||||
state.misc.overlay = Overlay::None;
|
||||
}
|
||||
|
||||
}
|
||||
if turn_finished || state.dirty {
|
||||
state.dirty = true;
|
||||
@@ -610,89 +596,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
||||
state.dirty = true;
|
||||
}
|
||||
|
||||
Action::RunWorkflow { script } => {
|
||||
// Open the Workflow overlay so the user can see progress.
|
||||
state.misc.overlay = Overlay::Workflow;
|
||||
state.dirty = true;
|
||||
|
||||
// Reset engine state before starting.
|
||||
state.workflow_engine.agents.clear();
|
||||
state.workflow_engine.findings.clear();
|
||||
|
||||
let turn_events = state.turn_events.clone();
|
||||
let turn_events_live = state.turn_events.clone();
|
||||
|
||||
state.push_toast(Toast::new(
|
||||
ToastKind::Info,
|
||||
format!("Starting workflow: {}…", script.chars().take(40).collect::<String>()),
|
||||
));
|
||||
|
||||
let session_dir = state.session_dir.clone();
|
||||
let workspace_roots = state.workspace_roots.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use crate::app::workflow::script::{ScriptPrimitive, ScriptOptions, WorkflowScript};
|
||||
use crate::app::workflow::engine::{LiveStateFn, AgentStatus};
|
||||
|
||||
// Parse the script string:
|
||||
// "prompt1 | prompt2 | prompt3" → Parallel of 3 agents
|
||||
// "prompt1 -> prompt2" → Pipeline of 2 stages
|
||||
// "prompt" → single Agent
|
||||
let parts_pipe: Vec<&str> = script.split('|').map(str::trim).collect();
|
||||
let parts_arrow: Vec<&str> = script.split("->").map(str::trim).collect();
|
||||
|
||||
let primitive = if parts_pipe.len() > 1 {
|
||||
ScriptPrimitive::Parallel(
|
||||
parts_pipe.iter().map(|p| ScriptPrimitive::Agent(p.to_string())).collect()
|
||||
)
|
||||
} else if parts_arrow.len() > 1 {
|
||||
ScriptPrimitive::Pipeline(
|
||||
parts_arrow.iter().map(|p| ScriptPrimitive::Agent(p.to_string())).collect()
|
||||
)
|
||||
} else {
|
||||
ScriptPrimitive::Agent(script.clone())
|
||||
};
|
||||
|
||||
let wf = WorkflowScript {
|
||||
name: script.chars().take(40).collect(),
|
||||
description: script.clone(),
|
||||
script: primitive,
|
||||
options: ScriptOptions::default(),
|
||||
};
|
||||
|
||||
// Build a live-state callback that pushes WorkflowAgentUpdate events
|
||||
// into the turn_events queue so the TUI panel updates in real time.
|
||||
let live: LiveStateFn = Arc::new(move |agent_id: String, agent_name: String, status: AgentStatus| {
|
||||
if let Ok(mut q) = turn_events_live.lock() {
|
||||
q.push_back(crate::app::state::runtime::TurnEvent::WorkflowAgentUpdate {
|
||||
agent_id: agent_id.clone(),
|
||||
agent_name,
|
||||
status,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let args: HashMap<String, String> = HashMap::new();
|
||||
let no_abort: Option<std::sync::Arc<std::sync::atomic::AtomicBool>> = None;
|
||||
let result = crate::app::workflow::engine::run_workflow_tracked(
|
||||
&wf, &args, &no_abort, Some(&live), &session_dir, &workspace_roots,
|
||||
);
|
||||
|
||||
let (kind, message) = match result {
|
||||
Ok(summary) => ("workflow_done".to_string(), summary),
|
||||
Err(e) => ("workflow_error".to_string(), format!("Workflow failed: {e}")),
|
||||
};
|
||||
|
||||
if let Ok(mut q) = turn_events.lock() {
|
||||
q.push_back(crate::app::state::runtime::TurnEvent::SystemNote {
|
||||
kind,
|
||||
message,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,12 +60,7 @@ pub fn apply_command(command: Command) -> Vec<Action> {
|
||||
Command::Compact => {
|
||||
vec![Action::Compact]
|
||||
}
|
||||
Command::WorkflowOpen => {
|
||||
vec![Action::OpenOverlay(Overlay::Workflow)]
|
||||
}
|
||||
Command::WorkflowRun { script } => {
|
||||
vec![Action::RunWorkflow { script }]
|
||||
}
|
||||
|
||||
Command::TodoOpen => {
|
||||
vec![Action::OpenOverlay(Overlay::Todo)]
|
||||
}
|
||||
|
||||
@@ -87,8 +87,7 @@ const COMMANDS: &[&str] = &[
|
||||
"/model",
|
||||
"/model ls",
|
||||
"/model add",
|
||||
"/workflow",
|
||||
"/workflow run",
|
||||
|
||||
"/todo",
|
||||
"/usage",
|
||||
"/compact",
|
||||
|
||||
@@ -50,7 +50,6 @@ pub enum Overlay {
|
||||
Settings,
|
||||
Bash,
|
||||
QuitConfirm,
|
||||
Workflow,
|
||||
|
||||
KeyInput,
|
||||
Editor,
|
||||
|
||||
@@ -17,10 +17,6 @@ pub enum Command {
|
||||
},
|
||||
ModelList,
|
||||
Compact,
|
||||
WorkflowOpen,
|
||||
WorkflowRun {
|
||||
script: String,
|
||||
},
|
||||
TodoOpen,
|
||||
UsageOpen,
|
||||
Unknown(String),
|
||||
@@ -67,14 +63,6 @@ pub fn parse_command(text: &str) -> Command {
|
||||
}
|
||||
"/model" => Command::ModelList,
|
||||
"/compact" => Command::Compact,
|
||||
"/workflow" if arg1.is_empty() => Command::WorkflowOpen,
|
||||
"/workflow" if arg1 == "run" && !arg2.is_empty() => Command::WorkflowRun {
|
||||
script: arg2.to_string(),
|
||||
},
|
||||
"/workflow" if arg1 == "run" => Command::WorkflowOpen,
|
||||
"/workflow" => Command::WorkflowRun {
|
||||
script: arg1.to_string(),
|
||||
},
|
||||
"/todo" => Command::TodoOpen,
|
||||
"/usage" => Command::UsageOpen,
|
||||
_ => Command::Unknown(cmd.to_string()),
|
||||
|
||||
+1
-1
@@ -281,7 +281,7 @@ fn apply_client_update(
|
||||
|
||||
Some("Bash") => Overlay::Bash,
|
||||
Some("QuitConfirm") => Overlay::QuitConfirm,
|
||||
Some("Workflow") => Overlay::Workflow,
|
||||
|
||||
|
||||
Some("KeyInput") => Overlay::KeyInput,
|
||||
Some("Editor") => Overlay::Editor,
|
||||
|
||||
+1
-3
@@ -32,9 +32,7 @@ Input:
|
||||
/help Show help
|
||||
/clear Clear screen
|
||||
/model Select AI model provider
|
||||
/workflow Open workflow panel
|
||||
/workflow run <p> Run a workflow with prompt <p>
|
||||
/mode workflow Open workflow panel
|
||||
|
||||
/todo Open task list
|
||||
/usage Open usage details
|
||||
/compact Compact conversation history
|
||||
|
||||
+4
-5
@@ -36,11 +36,13 @@ pub fn draw(frame: &mut Frame, state: &crate::app::state::rest::AppStateRest) {
|
||||
// since this sidebar holds three stacked widgets, not one.
|
||||
let show_sidebar = area.width > SIDEBAR_MIN_WIDTH;
|
||||
let (main_area, sidebar_area) = if show_sidebar {
|
||||
let has_workflow = !state.workflow_engine.agents.is_empty();
|
||||
let sidebar_width = if has_workflow { 48 } else { 30 };
|
||||
let h_chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([
|
||||
Constraint::Min(40),
|
||||
Constraint::Length(30),
|
||||
Constraint::Length(sidebar_width),
|
||||
])
|
||||
.split(area);
|
||||
(h_chunks[0], Some(h_chunks[1]))
|
||||
@@ -224,10 +226,7 @@ fn render_overlay(
|
||||
frame.render_widget(paragraph, overlay_area);
|
||||
}
|
||||
|
||||
// ── Workflow ──────────────────────────────────────────────────
|
||||
crate::app::state::types::Overlay::Workflow => {
|
||||
workflow::draw_workflow_panel(frame, overlay_area, state);
|
||||
}
|
||||
|
||||
|
||||
// ── Key Input ─────────────────────────────────────────────────
|
||||
crate::app::state::types::Overlay::KeyInput => {
|
||||
|
||||
+18
-6
@@ -13,16 +13,28 @@ use super::theme::Theme;
|
||||
/// Render the persistent right-hand dashboard: Workflow, Tasks, and Usage
|
||||
/// widgets stacked in three roughly-equal vertical thirds.
|
||||
pub fn draw_sidebar(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
||||
let has_workflow = !state.workflow_engine.agents.is_empty();
|
||||
|
||||
let constraints = if has_workflow {
|
||||
vec![
|
||||
Constraint::Ratio(1, 2),
|
||||
Constraint::Ratio(1, 4),
|
||||
Constraint::Ratio(1, 4),
|
||||
]
|
||||
} else {
|
||||
vec![
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
]
|
||||
};
|
||||
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
Constraint::Ratio(1, 3),
|
||||
])
|
||||
.constraints(constraints)
|
||||
.split(area);
|
||||
|
||||
super::workflow::draw_workflow_widget(frame, chunks[0], state);
|
||||
super::workflow::draw_workflow_panel(frame, chunks[0], state);
|
||||
draw_tasks_widget(frame, chunks[1], state);
|
||||
draw_usage_widget(frame, chunks[2], state);
|
||||
}
|
||||
|
||||
+1
-45
@@ -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, WorkflowAgent};
|
||||
use crate::app::workflow::engine::AgentState;
|
||||
|
||||
/// Icons for agent states.
|
||||
fn state_icon(state: AgentState) -> &'static str {
|
||||
@@ -74,7 +74,6 @@ pub fn draw_workflow_panel(frame: &mut Frame, area: Rect, state: &crate::app::st
|
||||
header_lines.push(Line::from(vec![
|
||||
Span::styled("/workflow run ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD)),
|
||||
Span::styled("<prompt>", Style::default().fg(Theme::TEXT_DIM)),
|
||||
Span::styled(" · Esc to close", Style::default().fg(Theme::TEXT_DIM)),
|
||||
]));
|
||||
header_lines.push(Line::from(vec![
|
||||
Span::styled("Status: ", Style::default().fg(Theme::TEXT_DIM)),
|
||||
@@ -207,47 +206,4 @@ 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)),
|
||||
])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user