use std::collections::HashSet;
use crate::config_edit::branch::GoverningConfig;
use crate::files_view::{self, FilesView, Preview};
use crate::inboxview::{self, InboxEntry};
use crate::keymap::InspectorTab;
use crate::rail::{Pin, Rail};
use crate::steps_view::{self, StepDetail, StepTab, StepsView};
use crate::theme;
use crate::transcript::{self, AutoExpand, Transcript};
use crate::workdiff::{self, Attempt, WorkFile};
#[derive(Default)]
pub struct Ephemera {
pub json_collapsed: HashSet<String>,
pub files_sel: Option<usize>,
pub tx_folded: HashSet<String>,
pub notch_sel: Option<usize>,
pub work_sel: Option<WorkFile>,
}
#[derive(Clone)]
pub struct TabData {
pub transcript: std::sync::Arc<Transcript>,
pub speaker: String,
pub raw: bool,
pub auto: AutoExpand,
pub steps: StepsView,
pub step_sel: Option<usize>,
pub step_detail: Option<StepDetail>,
pub step_tab: StepTab,
pub inbox: Vec<InboxEntry>,
pub files: FilesView,
pub file_preview: Option<Preview>,
pub work: Vec<Attempt>,
pub work_patch: Option<Preview>,
pub governing: Option<GoverningConfig>,
pub rail: Rail,
pub pin: Option<Pin>,
}
pub fn render(
ui: &mut egui::Ui,
tab: InspectorTab,
data: &TabData,
eph: &mut Ephemera,
) -> Option<String> {
if let Some(pin) = data.pin.as_ref().filter(|_| tab.pinnable()) {
pinned_banner(ui, pin, &mut eph.notch_sel);
}
render_tab(ui, tab, data, eph)
}
fn pinned_banner(ui: &mut egui::Ui, pin: &Pin, selected: &mut Option<usize>) {
const RELEASE: &str = "Release the mark and return every tab to now. No key of its own: \
Tab reaches it, Space presses it.";
let click = |ui: &mut egui::Ui, text: egui::RichText| {
ui.add(egui::Label::new(text).sense(egui::Sense::click()))
.on_hover_text(RELEASE)
.clicked()
};
let hit = ui
.horizontal(|ui| {
let mark = click(
ui,
egui::RichText::new(format!("as of {}", pin.short)).color(theme::BRAZEN),
);
let words = click(
ui,
egui::RichText::new(format!(
"— every tab below shows this conversation as it stood then; {} tokens spent \
by that point. Click here to come back.",
pin.tokens
))
.weak(),
);
mark || words
})
.inner;
if hit {
*selected = None;
}
}
fn render_tab(
ui: &mut egui::Ui,
tab: InspectorTab,
data: &TabData,
eph: &mut Ephemera,
) -> Option<String> {
match tab {
InspectorTab::Transcript => transcript::render(
ui,
&data.transcript,
&transcript::Reading {
speaker: data.speaker.clone(),
raw: data.raw,
auto: data.auto,
},
&mut eph.tx_folded,
&data.rail,
&mut eph.notch_sel,
),
InspectorTab::Steps => {
steps_view::render(
ui,
&data.steps,
data.step_sel,
data.step_detail.as_ref(),
data.step_tab,
&mut eph.json_collapsed,
data.raw,
);
None
}
InspectorTab::Inbox => {
inboxview::render(ui, &data.inbox, data.raw);
None
}
InspectorTab::Files => {
files_view::render(
ui,
&data.files,
data.file_preview.as_ref(),
&mut eph.files_sel,
);
None
}
InspectorTab::Config => {
render_config(ui, data.governing.as_ref());
None
}
InspectorTab::Work => {
workdiff::render(ui, &data.work, data.work_patch.as_ref(), &mut eph.work_sel);
None
}
}
}
fn render_config(ui: &mut egui::Ui, governing: Option<&GoverningConfig>) {
let Some(gov) = governing else {
ui.weak("no governing config for this agent");
return;
};
ui.strong(gov.frozen_label());
if let Some(name) = &gov.branch_name_if_tip_of_one {
ui.weak(format!("tip of config/{name}"));
}
egui::ScrollArea::vertical().show(ui, |ui| {
for file in &gov.files {
ui.monospace(file);
}
});
}
#[cfg(test)]
mod tests;