use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Speaker {
User,
Bot,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TranscriptLine {
pub speaker: Speaker,
pub text: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Transcript {
pub lines: Vec<TranscriptLine>,
}
impl Transcript {
pub fn new() -> Self {
Self::default()
}
pub fn push_user(&mut self, text: &str) {
self.push(Speaker::User, text);
}
pub fn push_bot(&mut self, text: &str) {
self.push(Speaker::Bot, text);
}
fn push(&mut self, speaker: Speaker, text: &str) {
if text.is_empty() {
return;
}
match self.lines.last_mut() {
Some(last) if last.speaker == speaker => last.text.push_str(text),
_ => self.lines.push(TranscriptLine {
speaker,
text: text.to_owned(),
}),
}
}
pub fn user_turns(&self) -> u64 {
self.lines
.iter()
.filter(|l| l.speaker == Speaker::User)
.count() as u64
}
pub fn bot_turns(&self) -> u64 {
self.lines
.iter()
.filter(|l| l.speaker == Speaker::Bot)
.count() as u64
}
pub fn render(&self) -> Vec<u8> {
let turns: Vec<Turn> = self
.lines
.iter()
.map(|l| Turn {
role: match l.speaker {
Speaker::User => "user",
Speaker::Bot => "bot",
},
text: &l.text,
})
.collect();
serde_json::to_vec(&turns).unwrap_or_else(|_| b"[]".to_vec())
}
}
#[derive(Debug, Serialize)]
struct Turn<'a> {
role: &'a str,
text: &'a str,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::Value;
#[test]
fn empty_transcript_renders_empty_json_array() {
let t = Transcript::new();
assert_eq!(t.render(), b"[]".to_vec());
}
#[test]
fn consecutive_same_speaker_deltas_merge_into_one_line() {
let mut t = Transcript::new();
t.push_user("hel");
t.push_user("lo ");
t.push_user("there");
assert_eq!(t.lines.len(), 1);
assert_eq!(t.lines[0].speaker, Speaker::User);
assert_eq!(t.lines[0].text, "hello there");
}
#[test]
fn speaker_switch_starts_a_new_line() {
let mut t = Transcript::new();
t.push_user("hi");
t.push_bot("hello, how can I help?");
t.push_user("a question");
assert_eq!(t.lines.len(), 3);
assert_eq!(t.lines[0].speaker, Speaker::User);
assert_eq!(t.lines[1].speaker, Speaker::Bot);
assert_eq!(t.lines[2].speaker, Speaker::User);
}
#[test]
fn empty_deltas_are_dropped() {
let mut t = Transcript::new();
t.push_user("");
t.push_bot("");
assert!(t.lines.is_empty());
t.push_user("ab");
t.push_user("");
t.push_user("cd");
assert_eq!(t.lines.len(), 1);
assert_eq!(t.lines[0].text, "abcd");
}
#[test]
fn turn_counts_tally_lines_per_speaker() {
let mut t = Transcript::new();
t.push_bot("hello, how can I help?");
t.push_user("book an appointment");
t.push_bot("sure, what day?");
t.push_user("tuesday"); t.push_user(" afternoon");
assert_eq!(t.user_turns(), 2, "two user lines");
assert_eq!(t.bot_turns(), 2, "two bot lines");
let empty = Transcript::new();
assert_eq!(empty.user_turns(), 0);
assert_eq!(empty.bot_turns(), 0);
}
#[test]
fn render_is_role_text_json_array() {
let mut t = Transcript::new();
t.push_user("hi");
t.push_bot("hello");
let json: Value = serde_json::from_slice(&t.render()).unwrap();
assert_eq!(
json,
serde_json::json!([
{ "role": "user", "text": "hi" },
{ "role": "bot", "text": "hello" },
])
);
}
}