use async_trait::async_trait;
use crate::traits::*;
use crate::types::*;
pub struct MockAsr {
pub transcript: String,
}
impl MockAsr {
pub fn new(transcript: impl Into<String>) -> Self {
Self {
transcript: transcript.into(),
}
}
}
#[async_trait]
impl AsrAdapter for MockAsr {
async fn transcribe(&self, _audio: &[AudioChunk]) -> Result<Transcript, AsrError> {
Ok(Transcript::new(self.transcript.clone()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsrCall {
pub samples: usize,
pub sample_rate: u32,
}
pub struct RecordingAsr {
calls: std::sync::Arc<std::sync::Mutex<Vec<AsrCall>>>,
transcripts: Vec<String>,
}
impl RecordingAsr {
pub fn new<S: Into<String>>(transcripts: impl IntoIterator<Item = S>) -> Self {
let transcripts: Vec<String> = transcripts.into_iter().map(Into::into).collect();
assert!(
!transcripts.is_empty(),
"RecordingAsr needs at least one transcript"
);
Self {
calls: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
transcripts,
}
}
pub fn calls(&self) -> std::sync::Arc<std::sync::Mutex<Vec<AsrCall>>> {
std::sync::Arc::clone(&self.calls)
}
}
#[async_trait]
impl AsrAdapter for RecordingAsr {
async fn transcribe(&self, audio: &[AudioChunk]) -> Result<Transcript, AsrError> {
let mut calls = self.calls.lock().expect("call log poisoned");
let index = calls.len();
calls.push(AsrCall {
samples: audio.iter().map(|c| c.samples.len()).sum(),
sample_rate: AudioChunk::sample_rate_of(audio).unwrap_or(0),
});
let text = self
.transcripts
.get(index)
.or_else(|| self.transcripts.last())
.expect("checked non-empty in new");
Ok(Transcript::new(text.clone()))
}
}
pub struct MockContextProvider {
pub snapshot: ContextSnapshot,
}
impl MockContextProvider {
pub fn new() -> Self {
Self {
snapshot: ContextSnapshot::default(),
}
}
pub fn with_app(mut self, name: impl Into<String>, field_type: FieldType) -> Self {
self.snapshot.app_name = Some(name.into());
self.snapshot.field_type = Some(field_type);
self
}
}
impl Default for MockContextProvider {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl ContextProvider for MockContextProvider {
async fn get_context(&self) -> ContextSnapshot {
self.snapshot.clone()
}
}
pub enum MockRefinerBehavior {
Passthrough,
Uppercase,
Fixed(String),
Fail(String),
}
pub struct MockRefiner {
pub behavior: MockRefinerBehavior,
}
impl MockRefiner {
pub fn passthrough() -> Self {
Self {
behavior: MockRefinerBehavior::Passthrough,
}
}
pub fn uppercase() -> Self {
Self {
behavior: MockRefinerBehavior::Uppercase,
}
}
pub fn fixed(text: impl Into<String>) -> Self {
Self {
behavior: MockRefinerBehavior::Fixed(text.into()),
}
}
pub fn failing(msg: impl Into<String>) -> Self {
Self {
behavior: MockRefinerBehavior::Fail(msg.into()),
}
}
}
#[async_trait]
impl LlmRefiner for MockRefiner {
async fn refine(&self, input: RefinementInput) -> Result<RefinementOutput, RefineError> {
match &self.behavior {
MockRefinerBehavior::Passthrough => Ok(RefinementOutput::TextInsertion {
text: input.raw_text,
formatting: None,
}),
MockRefinerBehavior::Uppercase => Ok(RefinementOutput::TextInsertion {
text: input.raw_text.to_uppercase(),
formatting: None,
}),
MockRefinerBehavior::Fixed(s) => Ok(RefinementOutput::TextInsertion {
text: s.clone(),
formatting: None,
}),
MockRefinerBehavior::Fail(msg) => Err(RefineError::Failed(msg.clone())),
}
}
}
pub struct MockEmitter {
pub outputs: std::sync::Arc<tokio::sync::Mutex<Vec<RefinementOutput>>>,
}
impl MockEmitter {
pub fn new() -> Self {
Self {
outputs: std::sync::Arc::new(tokio::sync::Mutex::new(Vec::new())),
}
}
pub fn outputs(&self) -> std::sync::Arc<tokio::sync::Mutex<Vec<RefinementOutput>>> {
self.outputs.clone()
}
}
impl Default for MockEmitter {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl OutputEmitter for MockEmitter {
async fn emit(&self, output: RefinementOutput) -> EmitResult {
self.outputs.lock().await.push(output);
EmitResult::ok()
}
async fn undo(&self) -> EmitResult {
let mut buf = self.outputs.lock().await;
if buf.pop().is_some() {
EmitResult::ok()
} else {
EmitResult::fail("nothing to undo")
}
}
}