use std::path::PathBuf;
use crate::ast::expand_includes;
use crate::event::EventLog;
use crate::provider::rig::StreamChunk;
use crate::runtime::Runner;
use crate::tui::chat_agent::ChatAgent;
use crate::tui::InputMode;
use super::super::views::{TuiView, View, ViewAction};
use super::types::Action;
use super::App;
impl App {
pub(crate) fn apply_action(&mut self, action: Action) {
match action {
Action::Quit => {
self.should_quit = true;
}
Action::SwitchView(view) => {
self.switch_to_view(view);
}
Action::ToggleTheme => {
self.toggle_theme();
}
Action::ScrollUp => {
self.handle_scroll_up();
}
Action::ScrollDown => {
self.handle_scroll_down();
}
Action::ScrollToTop => {
}
Action::ScrollToBottom => {
}
Action::TogglePause => {
self.state.workflow.paused = !self.state.workflow.paused;
}
Action::Step => {
if self.state.workflow.paused {
self.state.workflow.step_requested = true;
}
}
Action::FocusNext => {
}
Action::FocusPrev => {
}
Action::FocusPanel(_n) => {
}
Action::CycleTab => {
}
Action::CopyToClipboard => {
}
Action::RetryWorkflow => {
self.retry_workflow();
}
Action::ExportTrace => {
}
Action::ToggleBreakpoint => {
}
Action::MouseClickPanel(_panel_id) => {
}
Action::MouseScrollUp => {
self.handle_scroll_up();
}
Action::MouseScrollDown => {
self.handle_scroll_down();
}
Action::DismissNotification => {
self.state.dismiss_notification();
}
Action::DismissAllNotifications => {
self.state.dismiss_all_notifications();
}
Action::DismissError => {
}
Action::EnterFilter => {
self.input_mode = InputMode::Search;
}
Action::ExitFilter => {
self.input_mode = InputMode::Normal;
self.state.filter_query.clear();
}
Action::FilterInput(c) => {
self.state.filter_query.push(c);
}
Action::FilterBackspace => {
self.state.filter_query.pop();
}
Action::FilterDelete | Action::FilterCursorLeft | Action::FilterCursorRight => {
}
Action::FilterClear => {
self.state.filter_query.clear();
}
Action::SettingsNextField
| Action::SettingsPrevField
| Action::SettingsToggleEdit
| Action::SettingsInput(_)
| Action::SettingsBackspace
| Action::SettingsDelete
| Action::SettingsCancelEdit
| Action::SettingsSave
| Action::SettingsCursorLeft
| Action::SettingsCursorRight => {
}
Action::SetMode(mode) => {
self.state.ui.mode = mode;
}
Action::Continue => {}
Action::ViewSpecific(view_action) => {
self.apply_view_action(view_action);
}
}
}
fn apply_view_action(&mut self, action: ViewAction) {
match action {
ViewAction::ChatInfer(prompt) => {
self.command_view
.chat
.add_user_message(format!("/infer {}", prompt));
self.set_status("Inferring...");
self.spawn_chat_command(move |mut agent, tx| async move {
agent.set_stream_chunk_tx(tx.clone());
let response = agent.infer(&prompt).await.map_err(|e| e.to_string())?;
let _ = tx.send(StreamChunk::Done(response)).await;
Ok(())
});
}
ViewAction::ChatExec(cmd) => {
self.command_view
.chat
.add_user_message(format!("/exec {}", cmd));
self.set_status("Executing...");
self.spawn_chat_command(move |mut agent, tx| async move {
agent.set_stream_chunk_tx(tx.clone());
let output = agent.exec_command(&cmd).await.map_err(|e| e.to_string())?;
let _ = tx.send(StreamChunk::Done(output)).await;
Ok(())
});
}
ViewAction::ChatFetch(url, method) => {
self.command_view
.chat
.add_user_message(format!("/fetch {} {}", method, url));
self.set_status("Fetching...");
self.spawn_chat_command(move |mut agent, tx| async move {
agent.set_stream_chunk_tx(tx.clone());
let response = agent
.fetch(&url, &method)
.await
.map_err(|e| e.to_string())?;
let _ = tx.send(StreamChunk::Done(response)).await;
Ok(())
});
}
ViewAction::ChatInvoke(tool, server, params) => {
self.command_view.chat.add_user_message(format!(
"/invoke {} {}",
tool,
server.as_deref().unwrap_or("(auto)")
));
self.set_status("Invoking MCP tool...");
self.spawn_chat_command(move |agent, tx| async move {
let result = agent
.invoke(&tool, server.as_deref(), params)
.await
.map_err(|e| e.to_string())?;
let _ = tx.send(StreamChunk::Done(result)).await;
Ok(())
});
}
ViewAction::ChatAgent(goal, max_turns, extended, servers) => {
self.command_view
.chat
.add_user_message(format!("/agent {}", goal));
self.set_status("Running agent...");
self.spawn_chat_command(move |agent, tx| async move {
let result = agent
.run_agent(goal, max_turns, extended, servers)
.await
.map_err(|e| e.to_string())?;
let _ = tx.send(StreamChunk::Done(result)).await;
Ok(())
});
}
ViewAction::ChatClear => {
self.command_view.chat.messages.clear();
self.set_status("Chat cleared");
}
ViewAction::ChatModelSwitch(provider) => {
self.set_status(&format!("Switched to {:?}", provider));
tracing::debug!("ChatModelSwitch: {:?}", provider);
}
ViewAction::ChatMcp(_mcp_action) => {
self.set_status("MCP action received");
}
ViewAction::SendChatMessage(msg) => {
self.command_view.chat.add_user_message(msg.clone());
self.set_status("Message sent");
tracing::debug!("SendChatMessage: {}", msg);
}
ViewAction::RunWorkflow(path) => {
self.run_workflow(path);
}
ViewAction::OpenInStudio(path) => match self.studio_view.load_file(path.clone()) {
Ok(()) => {
self.switch_to_view(TuiView::Studio);
self.set_status(&format!("Opened: {}", path.display()));
}
Err(e) => {
self.set_status(&format!("Failed to open: {}", e));
tracing::error!("OpenInStudio failed: {}", e);
}
},
ViewAction::ValidateWorkflow(path) => {
self.set_status(&format!("Validating: {}", path.display()));
tracing::debug!("ValidateWorkflow: {}", path.display());
}
ViewAction::SetTheme(variant) => {
self.cosmic_theme = super::super::cosmic_theme::CosmicTheme::new(variant);
self.theme = self.cosmic_theme.as_theme();
self.set_status(&format!("Theme set to: {:?}", variant));
}
ViewAction::ToggleTheme => {
self.toggle_theme();
}
ViewAction::VerifyProviders => {
self.set_status("Verifying providers...");
self.spawn_provider_verification();
self.spawn_mcp_verification();
}
ViewAction::RefreshVerification => {
self.set_status("Refreshing verification...");
{
let mut cache = self.verification_cache.lock();
cache.invalidate_all();
}
self.spawn_provider_verification();
self.spawn_provider_verification_timeout();
self.spawn_mcp_verification();
}
ViewAction::ProviderSelectorConfirm { provider_id, model } => {
self.set_status(&format!("Selected: {} / {}", provider_id, model));
tracing::debug!("ProviderSelectorConfirm: {} / {}", provider_id, model);
}
ViewAction::PullNativeModel(model) => {
self.pull_native_model(model);
}
ViewAction::DeleteNativeModel(model) => {
self.delete_native_model(model);
}
ViewAction::RefreshNativeModels => {
self.refresh_native_models();
}
ViewAction::Error(msg) => {
self.set_status(&format!("Error: {}", msg));
}
ViewAction::LaunchWizard => {
self.should_launch_wizard = true;
self.should_quit = true;
self.set_status("Launching setup wizard...");
}
ViewAction::None
| ViewAction::Quit
| ViewAction::SwitchView(_)
| ViewAction::OpenControl => {
}
}
}
fn switch_to_view(&mut self, view: TuiView) {
if self.current_view == view {
return;
}
self.call_view_on_leave(self.current_view);
let old_view = self.current_view;
self.current_view = view;
self.state.dirty.mark_all();
self.input_mode = match view {
TuiView::Command => InputMode::Insert,
_ => InputMode::Normal,
};
self.call_view_on_enter(view);
tracing::debug!("Switched view: {:?} -> {:?}", old_view, view);
}
pub(super) fn call_view_on_enter(&mut self, view: TuiView) {
match view {
TuiView::Studio => self.studio_view.on_enter(&mut self.state),
TuiView::Command => self.command_view.on_enter(&mut self.state),
TuiView::Control => self.control_view.on_enter(&mut self.state),
}
}
fn call_view_on_leave(&mut self, view: TuiView) {
match view {
TuiView::Studio => self.studio_view.on_leave(&mut self.state),
TuiView::Command => self.command_view.on_leave(&mut self.state),
TuiView::Control => self.control_view.on_leave(&mut self.state),
}
}
fn handle_scroll_up(&mut self) {
}
fn handle_scroll_down(&mut self) {
}
fn run_workflow(&mut self, path: PathBuf) {
tracing::info!("Running workflow: {}", path.display());
let yaml_content = match std::fs::read_to_string(&path) {
Ok(content) => content,
Err(e) => {
self.set_status(&format!("Failed to read file: {}", e));
tracing::error!("Failed to read workflow file: {}", e);
return;
}
};
let workflow = match crate::ast::parse_workflow(&yaml_content) {
Ok(w) => w,
Err(e) => {
self.set_status(&format!("Parse error: {}", e));
tracing::error!("Failed to parse workflow: {}", e);
return;
}
};
let base_path = path
.parent()
.filter(|p| !p.as_os_str().is_empty())
.unwrap_or(std::path::Path::new("."));
let workflow = match expand_includes(workflow, base_path) {
Ok(w) => w,
Err(e) => {
self.set_status(&format!("Include error: {}", e));
tracing::error!("Failed to expand includes: {}", e);
return;
}
};
let workflow = match crate::ast::unlower(workflow) {
Ok(w) => w,
Err(e) => {
self.set_status(&format!("Unlower error: {}", e));
tracing::error!("Failed to unlower workflow: {}", e);
return;
}
};
let (event_log, event_rx) = EventLog::new_with_broadcast();
self.broadcast_rx = Some(event_rx);
let workflow_name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("workflow")
.to_string();
self.state.workflow = crate::tui::state::WorkflowState::new(workflow_name.clone());
self.state.tasks.clear();
self.workflow_done = false;
let mut runner = match Runner::with_event_log(workflow, event_log) {
Ok(r) => r.quiet(),
Err(e) => {
self.set_status(&format!("DAG error: {}", e));
tracing::error!("Failed to construct Runner DAG: {}", e);
return;
}
};
self.spawn_tracked(async move {
tracing::info!("Starting workflow execution: {}", workflow_name);
match runner.run().await {
Ok(output) => {
tracing::info!(
"Workflow '{}' completed: {} bytes output",
workflow_name,
output.len()
);
}
Err(e) => {
tracing::error!("Workflow '{}' failed: {}", workflow_name, e);
}
}
});
self.command_view.switch_to_monitor();
self.switch_to_view(TuiView::Command);
self.set_status(&format!("Running: {}", path.display()));
}
fn spawn_chat_command<F, Fut>(&mut self, op: F)
where
F: FnOnce(ChatAgent, tokio::sync::mpsc::Sender<StreamChunk>) -> Fut + Send + 'static,
Fut: std::future::Future<Output = std::result::Result<(), String>> + Send,
{
let tx = self.stream_chunk_tx.clone();
self.spawn_tracked(async move {
match ChatAgent::new() {
Ok(agent) => {
if let Err(msg) = op(agent, tx.clone()).await {
let _ = tx.send(StreamChunk::Error(msg)).await;
}
}
Err(e) => {
let _ = tx
.send(StreamChunk::Error(format!(
"Failed to create ChatAgent: {}",
e
)))
.await;
}
}
});
}
}