use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct AudioChunk {
pub samples: Vec<f32>,
pub sample_rate: u32,
pub channels: u16,
}
impl AudioChunk {
pub fn concat(chunks: &[AudioChunk]) -> Vec<f32> {
let total = chunks.iter().map(|c| c.samples.len()).sum();
let mut out = Vec::with_capacity(total);
for chunk in chunks {
out.extend_from_slice(&chunk.samples);
}
out
}
pub fn sample_rate_of(chunks: &[AudioChunk]) -> Option<u32> {
chunks.first().map(|c| c.sample_rate)
}
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct Transcript {
pub text: String,
pub confidence: f32,
pub duration: Option<Duration>,
}
impl Transcript {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
confidence: 1.0,
duration: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Language {
English,
Japanese,
Chinese,
Korean,
Spanish,
}
impl Language {
pub fn from_bcp47(tag: &str) -> Option<Self> {
let primary = tag.split(['-', '_']).next().unwrap_or(tag);
match primary.to_ascii_lowercase().as_str() {
"en" => Some(Self::English),
"ja" => Some(Self::Japanese),
"zh" => Some(Self::Chinese),
"ko" => Some(Self::Korean),
"es" => Some(Self::Spanish),
_ => None,
}
}
pub fn as_bcp47(&self) -> &'static str {
match self {
Self::English => "en",
Self::Japanese => "ja",
Self::Chinese => "zh",
Self::Korean => "ko",
Self::Spanish => "es",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
pub fn len(&self) -> usize {
self.end.saturating_sub(self.start)
}
pub fn is_empty(&self) -> bool {
self.end <= self.start
}
pub fn iou(&self, other: &Span) -> f64 {
if self.is_empty() || other.is_empty() {
return 0.0;
}
let inter_start = self.start.max(other.start);
let inter_end = self.end.min(other.end);
if inter_start >= inter_end {
return 0.0;
}
let inter = (inter_end - inter_start) as f64;
let union = (self.len() + other.len()) as f64 - inter;
if union <= 0.0 {
0.0
} else {
inter / union
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum FieldType {
CodeEditor,
EmailCompose,
ChatMessage,
Terminal,
Document,
SearchBar,
Generic,
}
#[derive(Debug, Clone, Default)]
pub struct ContextSnapshot {
pub app_name: Option<String>,
pub app_bundle_id: Option<String>,
pub field_content: Option<String>,
pub field_type: Option<FieldType>,
pub custom_dictionary: Vec<String>,
pub instructions: Option<String>,
pub locale: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RefinementMode {
Dictation,
Command,
Structured,
}
#[derive(Debug, Clone)]
pub struct RefinementInput {
pub raw_text: String,
pub context: ContextSnapshot,
pub mode: RefinementMode,
}
#[derive(Debug, Clone, Default)]
pub struct FormattingHint {
pub language: Option<String>,
pub style: Option<String>,
}
#[derive(Debug, Clone)]
pub enum RefinementOutput {
TextInsertion {
text: String,
formatting: Option<FormattingHint>,
},
Command {
action: String,
parameters: HashMap<String, String>,
},
StructuredInput {
intent: String,
text: Option<String>,
metadata: HashMap<String, String>,
},
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum EmitError {
#[error("output target unavailable: {0}")]
Unavailable(String),
#[error("unsupported output: {0}")]
Unsupported(String),
#[error("nothing to undo")]
NothingToUndo,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct EmitResult {
pub success: bool,
pub error: Option<EmitError>,
}
impl EmitResult {
pub fn ok() -> Self {
Self {
success: true,
error: None,
}
}
pub fn failed(error: EmitError) -> Self {
Self {
success: false,
error: Some(error),
}
}
pub fn fail(msg: impl Into<String>) -> Self {
Self::failed(EmitError::Unavailable(msg.into()))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ActivationMethod {
Hotkey(String),
PushToTalk,
Vad,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PipelineState {
Idle,
Activating,
Recording,
Processing,
Emitting,
Cancelling,
}