use crate::AppModel;
use crate::actions::{
ActionsState, draft_clears, message_enabled, new_prompt_enabled, stop_children_offered,
stop_enabled, verbs,
};
use crate::cli_outbound::{Binary, Cli};
use crate::git_tree::Agent;
use crate::names::SplitMix64;
use crate::start;
use std::io;
use std::path::{Path, PathBuf};
use super::ShellState;
pub fn composer(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
) {
let Some(ws) = model.focused_workspace().map(PathBuf::from) else {
return;
};
let agents: Vec<Agent> = model
.focused_tree()
.map(|t| t.agents.clone())
.unwrap_or_default();
state.actions.selected_branch = model.focused_agent().map(|a| a.agent_id.clone());
let target = state.actions.selected_branch.clone();
let mint_seed = state.start.mint_seed;
if let Some(agent) = target.as_deref() {
ui.weak(format!("→ message {agent}"));
} else {
ui.weak(
start::preview(
&model.start_bare_inputs(),
&mut SplitMix64::from_seed(mint_seed),
)
.preview,
);
ui.horizontal(|ui| {
ui.label("dir (optional):");
ui.text_edit_singleline(&mut state.actions.path_dir);
});
}
let text = state.actions.new_prompt_input.clone();
let edit = ui.add(
egui::TextEdit::singleline(&mut state.actions.new_prompt_input)
.desired_width(f32::INFINITY)
.hint_text(match target {
Some(_) => "message the selected conversation",
None => "start a conversation",
}),
);
if state.focus_composer {
edit.request_focus();
state.focus_composer = false;
}
let entered = edit.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
let ctx = VerbCtx {
ws,
agents,
text,
entered,
mint_seed,
};
verb_buttons(ui, model, state, lernie, bl, &ctx);
let join = model.focused_join().cloned();
super::ball_bar::actions(ui, model, &mut state.actions, bl, join.as_ref());
if let Some(failure) = &state.actions.last_failure {
super::failure_banner(ui, failure);
}
}
struct VerbCtx {
ws: PathBuf,
agents: Vec<Agent>,
text: String,
entered: bool,
mint_seed: u64,
}
fn verb_buttons(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
ctx: &VerbCtx,
) {
let actions = &mut state.actions;
let selected = actions.selected_branch.clone();
let state_root = model.state_root().to_path_buf();
let gate = model.toolchain().clone();
let lernie_ok = model.toolchain().permits(Binary::Lernie);
ui.horizontal(|ui| {
if let Some(agent) = selected.as_deref() {
let msg_on = message_enabled(Some(agent), &ctx.text, &ctx.agents) && lernie_ok;
let send = ui
.add_enabled(msg_on, egui::Button::new("Message"))
.clicked();
if msg_on && (send || ctx.entered) {
let sent = verbs::message(
&gate,
lernie,
&state_root,
&super::now_ts(),
&ctx.ws,
agent,
&ctx.text,
);
if after_lernie(sent, model, actions) {
actions.new_prompt_input.clear();
}
}
stop_controls(
ui,
model,
actions,
lernie,
(&gate, lernie_ok),
&ctx.ws,
&ctx.agents,
);
} else {
let on = new_prompt_enabled(&ctx.text) && lernie_ok;
let send = ui
.add_enabled(on, egui::Button::new("New prompt"))
.clicked();
if on && (send || ctx.entered) {
if super::fire::fire_start(model, actions, lernie, bl, ctx.mint_seed, &ctx.text) {
actions.new_prompt_input.clear();
actions.path_dir.clear();
}
}
}
if ui
.add_enabled(lernie_ok, egui::Button::new("Scan"))
.clicked()
{
let scanned = verbs::scan(&gate, lernie, &state_root, &super::now_ts(), &ctx.ws);
after_lernie(scanned, model, actions);
}
});
}
fn stop_controls(
ui: &mut egui::Ui,
model: &mut AppModel,
actions: &mut ActionsState,
lernie: &Cli,
gate: (&crate::world::toolgate::ToolchainState, bool),
ws: &Path,
agents: &[Agent],
) {
let Some(agent) = actions.selected_branch.clone() else {
return;
};
if stop_children_offered(&agent, agents) {
ui.checkbox(&mut actions.stop_children, "children");
}
let stop_on = stop_enabled(Some(&agent), agents) && gate.1;
if ui.add_enabled(stop_on, egui::Button::new("Stop")).clicked() {
let state_root = model.state_root().to_path_buf();
let stopped = verbs::stop(
gate.0,
lernie,
&state_root,
&super::now_ts(),
ws,
&agent,
actions.stop_children,
);
after_lernie(stopped, model, actions);
}
}
fn after_lernie(
result: io::Result<verbs::Outcome>,
model: &mut AppModel,
actions: &mut ActionsState,
) -> bool {
model.after_lernie_verb();
actions.last_failure = model.last_failure();
draft_clears(&result)
}