""" ASR + Audio Diarizer — speaker-separated evidence ledger A_t = {S_0(t), S_1(t), S_2(t), N(t), E(t)} S_0 = user, S_1 = TV/background, S_2 = other speaker, N = noise, E = events Browser-side speech recognition provides the transcript. This module assigns speaker labels and confidence. """ import hashlib import time from .stream_state import AudioChunk, sha256_text def process_transcript(text: str, state) -> AudioChunk: """Process a transcript chunk from the browser. The browser sends transcript text (from SpeechRecognition API). We assign speaker label "user" by default since it's the primary mic. """ chunk = AudioChunk( ts=time.time(), chunk_id=f"chunk_{int(time.time() * 1000)}", duration_ms=len(text) * 50, # rough estimate speaker="user", transcript=text.strip(), confidence=0.85, # browser ASR confidence placeholder sha256=sha256_text(text), is_speech=bool(text.strip()), ) return chunk def assign_speaker(text: str, existing_speakers: dict) -> str: """Heuristic speaker assignment. In a full system, this would use pyannote/WhisperX diarization. For now, we use simple heuristics: - If text matches known user patterns → "user" - If text seems like TV/background → "background" - Otherwise → "unknown" """ text_lower = text.lower() # Check if this looks like user instruction instruction_words = ["build", "make", "generate", "create", "show", "run", "fix", "code"] if any(w in text_lower for w in instruction_words): return "user" # Check if it matches existing user transcript patterns if "user" in existing_speakers: user_text = existing_speakers["user"].get("transcript", "").lower() # Simple word overlap check user_words = set(user_text.split()) text_words = set(text_lower.split()) overlap = len(user_words & text_words) if overlap > 2: return "user" # Default to unknown (could be TV, background, another person) return "unknown"