use crate::AppModel;
use crate::budgets;
use crate::cli_outbound::Cli;
use crate::git_tree::Agent;
use crate::nav::convs::root_of;
use crate::theme;
use std::path::{Path, PathBuf};
use super::ShellState;
pub fn center(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
bz: &Cli,
) {
let Some(ws) = model.focused_workspace().map(PathBuf::from) else {
bootstrap(ui, model, state, lernie, bl);
return;
};
if model.focused_is_replay() {
ui.weak("replay · read-only");
}
let Some(agent) = model.focused_agent() else {
ui.weak("select a conversation — or start one below");
return;
};
let agent_id = agent.agent_id.clone();
let agent_state = agent.state;
let agents: Vec<Agent> = model
.focused_tree()
.map(|t| t.agents.clone())
.unwrap_or_default();
let root = root_of(&agents, &agent_id).unwrap_or_else(|| agent_id.clone());
header(ui, model, &ws, &root);
members_tree(ui, model, &ws, &agents, &agent_id);
if crate::login::auth::latest_step_auth_failed(&ws, &agent_id, agent_state) {
ui.colored_label(
theme::ICHOR,
"⚠ the last step failed on credentials — log in below",
);
let state_root = model.state_root().to_path_buf();
super::toolchain::login_section(ui, &mut state.login, bz, &state_root);
}
if crate::steps_view::latest_step_no_response(&ws, &agent_id, agent_state) {
ui.colored_label(
theme::ICHOR,
format!(
"⚠ {} — the driver's own stderr is in the activity trail below",
crate::steps_view::NO_RESPONSE
),
);
}
ui.separator();
super::inspector::tabs_and_content(ui, model, &mut state.inspector, &ws);
}
fn header(ui: &mut egui::Ui, model: &AppModel, ws: &Path, root: &str) {
ui.heading(root);
if let Some(ball) = model.conversation_ball(root) {
super::conv_ball::header_ball(ui, &ball);
}
let ws_balls = model.ws_balls(ws);
if !ws_balls.is_empty() {
ui.horizontal(|ui| {
ui.weak("workspace balls:");
for ball in &ws_balls {
match &ball.badge {
Some(b) => ui.weak(format!("{} · {b}", ball.id)),
None => ui.weak(&ball.id),
};
}
});
}
let spend = budgets::spend(ws, root);
budgets::render(ui, &spend);
}
fn members_tree(
ui: &mut egui::Ui,
model: &mut AppModel,
ws: &Path,
agents: &[Agent],
selected: &str,
) {
let rows = model.conversation_members();
if rows.len() <= 1 {
return;
}
for row in rows {
let Some(agent) = agents.get(row.index) else {
continue;
};
member_row(ui, model, ws, agent, row.depth, selected);
}
}
fn member_row(
ui: &mut egui::Ui,
model: &mut AppModel,
ws: &Path,
agent: &Agent,
depth: usize,
selected: &str,
) {
ui.horizontal(|ui| {
if depth > 0 {
ui.monospace(" ".repeat(depth));
}
let (glyph, color) = theme::state_badge(agent.state);
ui.colored_label(color, glyph);
if agent.state_uncertain {
ui.colored_label(theme::SIGIL, "?");
}
let is_selected = agent.agent_id == selected;
if ui.selectable_label(is_selected, &agent.agent_id).clicked() {
model.focus_agent(ws, &agent.agent_id);
}
if agent.conflicted_oid.is_some() {
ui.colored_label(theme::ICHOR, "declined-transfer");
}
if agent.budget_oid.is_some() {
ui.colored_label(theme::BRAZEN, "budget-exhausted");
}
if agent.pending_messages > 0 {
ui.colored_label(theme::BRAZEN, format!("✉{}", agent.pending_messages));
}
for call in &agent.tool_calls {
if call.state == crate::git_tree::ToolCallState::InFlight {
let time = ui.ctx().input(|i| i.time);
ui.colored_label(theme::pulse(time), format!("⚙ {}", call.tool_id));
ui.ctx().request_repaint_after(theme::PULSE_REPAINT_DELAY);
}
}
});
}
fn bootstrap(
ui: &mut egui::Ui,
model: &mut AppModel,
state: &mut ShellState,
lernie: &Cli,
bl: &Cli,
) {
let seed = state.start.mint_seed;
ui.vertical_centered(|ui| {
crate::theme::wordmark(ui);
ui.weak(crate::theme::TAGLINE);
let inputs = model.start_bare_inputs();
ui.weak(
crate::start::preview(&inputs, &mut crate::names::SplitMix64::from_seed(seed)).preview,
);
});
let text = state.actions.new_prompt_input.clone();
ui.label("start a conversation:");
let edit = ui.add(
egui::TextEdit::singleline(&mut state.actions.new_prompt_input)
.desired_width(f32::INFINITY)
.hint_text("say what you want done"),
);
let focused_once = egui::Id::new("bootstrap-composer-focused");
if !ui.data(|d| d.get_temp::<bool>(focused_once).unwrap_or(false)) {
edit.request_focus();
ui.data_mut(|d| d.insert_temp(focused_once, true));
}
let submit_enabled = crate::actions::new_prompt_enabled(&text);
let entered = edit.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
let clicked = ui
.add_enabled(submit_enabled, egui::Button::new("Start"))
.clicked();
if submit_enabled
&& (clicked || entered)
&& super::fire::fire_bare(model, &mut state.actions, lernie, bl, seed, &text)
{
state.actions.new_prompt_input.clear();
}
if let Some(failure) = &state.actions.last_failure {
super::failure_banner(ui, failure);
}
}