use super::fold;
use crate::ui::theme::Speaker;
mod live;
mod pair;
mod role;
mod row;
pub use row::Row;
pub mod sent;
use crate::reply::stream::Stream;
use crate::reply::transcript::{Block, Entry, EntryKind, Transcript};
use live::{half, live_rows, streaming};
pub(crate) use role::OPERATOR;
use role::delivered;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Kind {
#[default]
Said,
Call,
Answered,
Result,
Thinking,
}
pub const WOUND: &str = "it stopped";
pub fn rows(transcript: &Transcript, live: Option<&Stream>) -> Vec<Row> {
let mut rows = pair::met(
transcript
.entries
.iter()
.flat_map(|entry| {
let rows = match (&entry.kind, live) {
(EntryKind::Streaming { .. }, Some(fold)) => streaming(fold),
_ => entry_rows(entry),
};
rows.into_iter().map(|row| row.from(&entry.name))
})
.collect(),
);
let running = transcript
.entries
.iter()
.any(|entry| matches!(entry.kind, EntryKind::Streaming { .. }));
role::settle(&mut rows, running);
rows
}
pub(super) const RETURNED: &str = "returned";
pub(super) const FAILED: &str = "failed";
fn entry_rows(entry: &Entry) -> Vec<Row> {
match &entry.kind {
EntryKind::Delivered {
sender,
sender_name,
epitaph,
body,
} => {
let who = crate::reply::transcript::said_by(sender, sender_name.as_deref());
let weight = delivered(sender, epitaph.as_ref());
vec![Row::plain(
match epitaph {
Some(word) => format!("{who} ({word})"),
None => who,
},
body.clone(),
weight,
)]
}
EntryKind::Model {
model_id, blocks, ..
} => blocks.iter().filter_map(|b| block(model_id, b)).collect(),
EntryKind::ToolResult {
tool_use_id,
content,
is_error,
} => vec![Row::result(
tool_use_id,
content.clone(),
*is_error,
fold::of(content),
)],
EntryKind::Streaming { thinking, text } => live_rows(thinking, text),
EntryKind::Compacted {
first,
last,
summary,
} => vec![Row::plain(
format!("compacted {first}–{last}"),
summary.clone(),
Speaker::Peer,
)],
EntryKind::Wounded {
wound,
reason,
auth_row,
} => vec![Row {
failed: true,
..Row::plain(
WOUND.to_owned(),
crate::reply::transcript::wounded(wound, reason.as_deref(), auth_row.as_deref()),
Speaker::Peer,
)
}],
EntryKind::Raw => vec![Row::plain(
format!("{} (unparsed)", entry.name),
entry.raw.clone(),
Speaker::Peer,
)],
EntryKind::Unknown(word) => vec![Row::plain(
format!(
"{} ({word}, which this version of lernie cannot read)",
entry.name
),
entry.raw.clone(),
Speaker::Peer,
)],
}
}
fn block(model_id: &str, block: &Block) -> Option<Row> {
match block {
Block::Text(text) => half(model_id, "", text),
Block::Thinking(text) => {
half(model_id, "thinking", text).map(|row| row.as_(Kind::Thinking))
}
Block::ToolUse { id, name, input } => Some(Row::call(name, input, id)),
Block::Unknown(word) => Some(Row::plain(
format!("{model_id} ({word}, which this version of lernie cannot read)"),
String::new(),
Speaker::Model,
)),
}
}