refactor: Remove workflow-related commands and overlays from the application
This commit is contained in:
@@ -81,9 +81,7 @@ pub enum Action {
|
|||||||
ModelList,
|
ModelList,
|
||||||
AbortTurn,
|
AbortTurn,
|
||||||
Compact,
|
Compact,
|
||||||
RunWorkflow {
|
|
||||||
script: String,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply an `Action` to the application state.
|
/// 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.agents.clear();
|
||||||
state.workflow_engine.findings.clear();
|
state.workflow_engine.findings.clear();
|
||||||
}
|
}
|
||||||
if message.to_lowercase().contains("complete")
|
// popup removed, no overlay to reset
|
||||||
&& state.misc.overlay == Overlay::Workflow
|
|
||||||
{
|
|
||||||
state.misc.overlay = Overlay::None;
|
|
||||||
}
|
|
||||||
state.push_toast(Toast {
|
state.push_toast(Toast {
|
||||||
kind: ToastKind::Info,
|
kind: ToastKind::Info,
|
||||||
message: message.clone(),
|
message: message.clone(),
|
||||||
@@ -435,9 +429,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
|||||||
crate::dto::chat::message::Role::System,
|
crate::dto::chat::message::Role::System,
|
||||||
format!("✓ {message}"),
|
format!("✓ {message}"),
|
||||||
));
|
));
|
||||||
if state.misc.overlay == Overlay::Workflow {
|
// overlay removed
|
||||||
state.misc.overlay = Overlay::None;
|
|
||||||
}
|
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
} else if kind == "workflow_error" {
|
} else if kind == "workflow_error" {
|
||||||
state.push_toast(Toast {
|
state.push_toast(Toast {
|
||||||
@@ -450,9 +442,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
|||||||
crate::dto::chat::message::Role::System,
|
crate::dto::chat::message::Role::System,
|
||||||
format!("✗ {message}"),
|
format!("✗ {message}"),
|
||||||
));
|
));
|
||||||
if state.misc.overlay == Overlay::Workflow {
|
// overlay removed
|
||||||
state.misc.overlay = Overlay::None;
|
|
||||||
}
|
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
} else {
|
} else {
|
||||||
state.push_toast(Toast::new(ToastKind::Info, message));
|
state.push_toast(Toast::new(ToastKind::Info, message));
|
||||||
@@ -537,18 +527,14 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
|||||||
status,
|
status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if state.misc.overlay != Overlay::Workflow {
|
// popup removed
|
||||||
state.misc.overlay = Overlay::Workflow;
|
|
||||||
}
|
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if turn_finished {
|
if turn_finished {
|
||||||
maybe_trigger_review(state);
|
maybe_trigger_review(state);
|
||||||
if state.misc.overlay == Overlay::Workflow {
|
|
||||||
state.misc.overlay = Overlay::None;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if turn_finished || state.dirty {
|
if turn_finished || state.dirty {
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
@@ -610,89 +596,7 @@ pub fn apply_action(state: &mut AppStateRest, action: Action) {
|
|||||||
state.dirty = true;
|
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 => {
|
Command::Compact => {
|
||||||
vec![Action::Compact]
|
vec![Action::Compact]
|
||||||
}
|
}
|
||||||
Command::WorkflowOpen => {
|
|
||||||
vec![Action::OpenOverlay(Overlay::Workflow)]
|
|
||||||
}
|
|
||||||
Command::WorkflowRun { script } => {
|
|
||||||
vec![Action::RunWorkflow { script }]
|
|
||||||
}
|
|
||||||
Command::TodoOpen => {
|
Command::TodoOpen => {
|
||||||
vec![Action::OpenOverlay(Overlay::Todo)]
|
vec![Action::OpenOverlay(Overlay::Todo)]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,8 +87,7 @@ const COMMANDS: &[&str] = &[
|
|||||||
"/model",
|
"/model",
|
||||||
"/model ls",
|
"/model ls",
|
||||||
"/model add",
|
"/model add",
|
||||||
"/workflow",
|
|
||||||
"/workflow run",
|
|
||||||
"/todo",
|
"/todo",
|
||||||
"/usage",
|
"/usage",
|
||||||
"/compact",
|
"/compact",
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ pub enum Overlay {
|
|||||||
Settings,
|
Settings,
|
||||||
Bash,
|
Bash,
|
||||||
QuitConfirm,
|
QuitConfirm,
|
||||||
Workflow,
|
|
||||||
|
|
||||||
KeyInput,
|
KeyInput,
|
||||||
Editor,
|
Editor,
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ pub enum Command {
|
|||||||
},
|
},
|
||||||
ModelList,
|
ModelList,
|
||||||
Compact,
|
Compact,
|
||||||
WorkflowOpen,
|
|
||||||
WorkflowRun {
|
|
||||||
script: String,
|
|
||||||
},
|
|
||||||
TodoOpen,
|
TodoOpen,
|
||||||
UsageOpen,
|
UsageOpen,
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
@@ -67,14 +63,6 @@ pub fn parse_command(text: &str) -> Command {
|
|||||||
}
|
}
|
||||||
"/model" => Command::ModelList,
|
"/model" => Command::ModelList,
|
||||||
"/compact" => Command::Compact,
|
"/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,
|
"/todo" => Command::TodoOpen,
|
||||||
"/usage" => Command::UsageOpen,
|
"/usage" => Command::UsageOpen,
|
||||||
_ => Command::Unknown(cmd.to_string()),
|
_ => Command::Unknown(cmd.to_string()),
|
||||||
|
|||||||
+1
-1
@@ -281,7 +281,7 @@ fn apply_client_update(
|
|||||||
|
|
||||||
Some("Bash") => Overlay::Bash,
|
Some("Bash") => Overlay::Bash,
|
||||||
Some("QuitConfirm") => Overlay::QuitConfirm,
|
Some("QuitConfirm") => Overlay::QuitConfirm,
|
||||||
Some("Workflow") => Overlay::Workflow,
|
|
||||||
|
|
||||||
Some("KeyInput") => Overlay::KeyInput,
|
Some("KeyInput") => Overlay::KeyInput,
|
||||||
Some("Editor") => Overlay::Editor,
|
Some("Editor") => Overlay::Editor,
|
||||||
|
|||||||
+1
-3
@@ -32,9 +32,7 @@ Input:
|
|||||||
/help Show help
|
/help Show help
|
||||||
/clear Clear screen
|
/clear Clear screen
|
||||||
/model Select AI model provider
|
/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
|
/todo Open task list
|
||||||
/usage Open usage details
|
/usage Open usage details
|
||||||
/compact Compact conversation history
|
/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.
|
// since this sidebar holds three stacked widgets, not one.
|
||||||
let show_sidebar = area.width > SIDEBAR_MIN_WIDTH;
|
let show_sidebar = area.width > SIDEBAR_MIN_WIDTH;
|
||||||
let (main_area, sidebar_area) = if show_sidebar {
|
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()
|
let h_chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Min(40),
|
Constraint::Min(40),
|
||||||
Constraint::Length(30),
|
Constraint::Length(sidebar_width),
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
(h_chunks[0], Some(h_chunks[1]))
|
(h_chunks[0], Some(h_chunks[1]))
|
||||||
@@ -224,10 +226,7 @@ fn render_overlay(
|
|||||||
frame.render_widget(paragraph, overlay_area);
|
frame.render_widget(paragraph, overlay_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Workflow ──────────────────────────────────────────────────
|
|
||||||
crate::app::state::types::Overlay::Workflow => {
|
|
||||||
workflow::draw_workflow_panel(frame, overlay_area, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Key Input ─────────────────────────────────────────────────
|
// ── Key Input ─────────────────────────────────────────────────
|
||||||
crate::app::state::types::Overlay::KeyInput => {
|
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
|
/// Render the persistent right-hand dashboard: Workflow, Tasks, and Usage
|
||||||
/// widgets stacked in three roughly-equal vertical thirds.
|
/// widgets stacked in three roughly-equal vertical thirds.
|
||||||
pub fn draw_sidebar(frame: &mut Frame, area: Rect, state: &crate::app::state::rest::AppStateRest) {
|
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()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints(constraints)
|
||||||
Constraint::Ratio(1, 3),
|
|
||||||
Constraint::Ratio(1, 3),
|
|
||||||
Constraint::Ratio(1, 3),
|
|
||||||
])
|
|
||||||
.split(area);
|
.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_tasks_widget(frame, chunks[1], state);
|
||||||
draw_usage_widget(frame, chunks[2], 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::widgets::{Block, Borders, Paragraph, Wrap};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
use super::theme::Theme;
|
use super::theme::Theme;
|
||||||
use crate::app::workflow::engine::{AgentState, WorkflowAgent};
|
use crate::app::workflow::engine::AgentState;
|
||||||
|
|
||||||
/// Icons for agent states.
|
/// Icons for agent states.
|
||||||
fn state_icon(state: AgentState) -> &'static str {
|
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![
|
header_lines.push(Line::from(vec![
|
||||||
Span::styled("/workflow run ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD)),
|
Span::styled("/workflow run ", Style::default().fg(Theme::PRIMARY).add_modifier(Modifier::BOLD)),
|
||||||
Span::styled("<prompt>", Style::default().fg(Theme::TEXT_DIM)),
|
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![
|
header_lines.push(Line::from(vec![
|
||||||
Span::styled("Status: ", Style::default().fg(Theme::TEXT_DIM)),
|
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
|
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