use tokio::sync::oneshot;
use super::{
AgentViewTarget, App, ChatMessage, MessageRole, TRANSCRIPT_MAX_ENTRIES, TranscriptCache,
TuiTranscriptEntry, load_transcript_file,
};
impl App {
pub fn set_view_target(&mut self, target: AgentViewTarget) {
if self.sessions.current().view_target == target {
return;
}
self.sessions.current_mut().view_target = target;
self.sessions.current_mut().render_cache.clear();
self.sessions.current_mut().scroll_offset = 0;
self.sessions.current_mut().transcript_cache = None;
self.sessions.current_mut().pending_transcript = None;
if let AgentViewTarget::SubAgent { ref id, .. } = self.sessions.current().view_target {
let id = id.clone();
self.start_transcript_load(&id);
}
}
fn start_transcript_load(&mut self, agent_id: &str) {
let transcript_path = self
.metrics
.sub_agents
.iter()
.find(|sa| sa.id == agent_id)
.and_then(|sa| sa.transcript_dir.as_deref())
.map(|dir| std::path::PathBuf::from(dir).join(format!("{agent_id}.jsonl")));
let Some(path) = transcript_path else {
return;
};
let (tx, rx) = oneshot::channel();
self.sessions.current_mut().pending_transcript = Some(rx);
let is_active = self
.metrics
.sub_agents
.iter()
.find(|sa| sa.id == agent_id)
.is_some_and(|sa| matches!(sa.state.as_str(), "working" | "submitted"));
tokio::task::spawn_blocking(move || {
let result = load_transcript_file(&path, is_active);
let _ = tx.send(result);
});
}
pub fn poll_pending_transcript(&mut self) {
let Some(rx) = self.sessions.current_mut().pending_transcript.as_mut() else {
return;
};
match rx.try_recv() {
Ok((entries, total)) => {
self.sessions.current_mut().pending_transcript = None;
let turns_at_load = self
.sessions
.current()
.view_target
.subagent_id()
.and_then(|id| self.metrics.sub_agents.iter().find(|sa| sa.id == id))
.map_or(0, |sa| sa.turns_used);
if let AgentViewTarget::SubAgent { ref id, .. } =
self.sessions.current().view_target.clone()
{
self.sessions.current_mut().transcript_cache = Some(TranscriptCache {
agent_id: id.clone(),
entries,
turns_at_load,
total_in_file: total,
});
}
self.sessions.current_mut().render_cache.clear();
}
Err(oneshot::error::TryRecvError::Empty) => {}
Err(oneshot::error::TryRecvError::Closed) => {
self.sessions.current_mut().pending_transcript = None;
}
}
}
pub(super) fn maybe_reload_transcript(&mut self) {
let AgentViewTarget::SubAgent { ref id, .. } = self.sessions.current().view_target.clone()
else {
return;
};
if self.sessions.current().pending_transcript.is_some() {
return;
}
let current_turns = self
.metrics
.sub_agents
.iter()
.find(|sa| sa.id == *id)
.map_or(0, |sa| sa.turns_used);
let cached_turns = self
.sessions
.current()
.transcript_cache
.as_ref()
.map_or(0, |c| c.turns_at_load);
if current_turns > cached_turns {
let agent_id = id.to_owned();
self.start_transcript_load(&agent_id);
}
}
#[must_use]
pub fn visible_messages(&self) -> Vec<ChatMessage> {
let slot = self.sessions.current();
if slot.view_target.is_main() {
return slot.messages.clone();
}
if let Some(ref cache) = slot.transcript_cache {
return cache
.entries
.iter()
.map(TuiTranscriptEntry::to_chat_message)
.collect();
}
if slot.pending_transcript.is_some() {
return vec![ChatMessage::new(
MessageRole::System,
"Loading transcript...".to_owned(),
)];
}
let name = slot.view_target.subagent_name().unwrap_or("unknown");
vec![ChatMessage::new(
MessageRole::System,
format!("Transcript not available for {name}."),
)]
}
#[must_use]
pub fn transcript_truncation_info(&self) -> Option<String> {
let cache = self.sessions.current().transcript_cache.as_ref()?;
if cache.total_in_file > TRANSCRIPT_MAX_ENTRIES {
Some(format!(
"[showing last {TRANSCRIPT_MAX_ENTRIES} of {} messages]",
cache.total_in_file
))
} else {
None
}
}
}