use super::{Row, RowClass, Tone};
use crate::theme::{Role, message_role};
use crate::transcript::{Block, Entry, EntryKind, Transcript};
mod build;
pub(crate) use build::key;
use build::{row, with_size};
pub(super) fn push_entry(
transcript: &Transcript,
entry: &Entry,
speaker: &str,
out: &mut Vec<Row>,
) {
match &entry.kind {
EntryKind::Delivered {
sender,
epitaph,
body,
} if body.is_empty() => out.push(row(
key(&entry.name, 0),
delivered_prefix(sender, epitaph.as_ref()),
"(no message body)",
RowClass::Response,
Tone::Weak,
Some(message_role(sender, epitaph.is_some())),
)),
EntryKind::Delivered {
sender,
epitaph,
body,
} => out.push(row(
key(&entry.name, 0),
delivered_prefix(sender, epitaph.as_ref()),
body,
RowClass::Response,
Tone::Plain,
Some(message_role(sender, epitaph.is_some())),
)),
EntryKind::Model {
model_id, blocks, ..
} if blocks.is_empty() => out.push(Row {
hover: model_hover(model_id),
..row(
key(&entry.name, 0),
format!("{speaker}:"),
"(no content blocks)",
RowClass::Other,
Tone::Weak,
Some(Role::Model),
)
}),
EntryKind::Model {
model_id, blocks, ..
} => {
for (i, block) in blocks.iter().enumerate() {
out.push(block_row(
transcript,
key(&entry.name, i),
speaker,
model_id,
block,
));
}
}
EntryKind::ToolResult {
content, is_error, ..
} => {
let (glyph, _, phrase) = crate::theme::tool_result_badge(*is_error);
let tone = if *is_error { Tone::Bad } else { Tone::Good };
out.push(with_size(row(
key(&entry.name, 0),
format!("{glyph} {phrase}"),
content,
RowClass::Other,
tone,
None,
)));
}
EntryKind::Streaming { thinking, text } => {
if !thinking.is_empty() {
out.push(row(
key(&entry.name, 0),
"thinking:".to_string(),
thinking,
RowClass::Other,
Tone::Live,
None,
));
}
if !text.is_empty() {
out.push(row(
key(&entry.name, 1),
"live:".to_string(),
text,
RowClass::Response,
Tone::Live,
Some(Role::Model),
));
}
}
EntryKind::Raw => out.push(row(
key(&entry.name, 0),
entry.name.clone(),
&String::from_utf8_lossy(&entry.raw),
RowClass::Other,
Tone::Weak,
None,
)),
}
}
fn block_row(
transcript: &Transcript,
key: String,
speaker: &str,
model_id: &str,
block: &Block,
) -> Row {
match block {
Block::Text(text) => Row {
hover: model_hover(model_id),
..row(
key,
format!("{speaker}:"),
text,
RowClass::Response,
Tone::Plain,
Some(Role::Model),
)
},
Block::Thinking(text) => row(
key,
"thinking:".to_string(),
text,
RowClass::Other,
Tone::Weak,
None,
),
Block::ToolUse {
id,
name,
input_summary,
} => {
let running = transcript.tool_in_progress(id);
let prefix = if running {
format!("⚙ {name} — running")
} else {
format!("⚙ {name}")
};
let tone = if running { Tone::InFlight } else { Tone::Plain };
row(key, prefix, input_summary, RowClass::Other, tone, None)
}
}
}
fn delivered_prefix(sender: &str, epitaph: Option<&crate::inboxview::Epitaph>) -> String {
match epitaph {
Some(epitaph) => format!("{sender} ended: {}", epitaph.label()),
None => format!("{sender}:"),
}
}
fn model_hover(model_id: &str) -> String {
format!("ran on {model_id} — the model is config, not the speaker")
}