use super::super::LIVE;
use super::{Kind, Row};
use crate::reply::stream::Stream;
use crate::reply::stream::window::Window;
use crate::ui::theme::Speaker;
pub(super) fn streaming(stream: &Stream) -> Vec<Row> {
let mut rows = live_rows(
stream.thinking.as_deref().unwrap_or_default(),
stream.text.as_deref().unwrap_or_default(),
);
rows.extend(stream.tools.iter().flat_map(call));
rows
}
fn call(window: &Window) -> Vec<Row> {
let named = window
.tool
.clone()
.unwrap_or_else(|| window.tool_use.clone());
let parked = window.held.as_ref().map(|reason| {
Row::plain(
format!("{LIVE} {HELD} {named} {}", window.tool_use),
reason.clone(),
Speaker::Model,
)
.as_(Kind::Call)
});
let dispatched = window.input.is_some() || window.held.is_none();
let opened = dispatched.then(|| {
Row::plain(
format!("{LIVE} → {named} {}", window.tool_use),
window.input.clone().unwrap_or_default(),
Speaker::Model,
)
.as_(Kind::Call)
});
let landed = window.exit_code.map(|code| {
let ok = code == 0;
Row {
who: format!("{} {}", window.tool_use, if ok { RETURNED } else { FAILED }),
said: format!("exit {code}"),
weight: Speaker::Peer,
failed: !ok,
fold: None,
kind: Kind::Result,
}
});
parked.into_iter().chain(opened).chain(landed).collect()
}
const RETURNED: &str = "returned";
const FAILED: &str = "failed";
const HELD: &str = "held";
pub(super) fn half(speaker: &str, mark: &str, said: &str) -> Option<Row> {
(!said.is_empty()).then(|| {
Row::plain(
if mark.is_empty() {
speaker.to_owned()
} else {
format!("{speaker} ({mark})")
},
said.to_owned(),
Speaker::Model,
)
})
}
pub(super) fn live_rows(thinking: &str, text: &str) -> Vec<Row> {
[
("thinking", thinking, Kind::Thinking),
("", text, Kind::Said),
]
.into_iter()
.filter_map(|(mark, said, kind)| half(LIVE, mark, said).map(|row| row.as_(kind)))
.collect()
}
#[cfg(test)]
mod tests;