From f6d93db381c0d83517a0a3451cd3e674d5c8c922 Mon Sep 17 00:00:00 2001 From: Ali Alavi Date: Fri, 2 Oct 2026 09:55:16 +0000 Subject: [PATCH] feat: ultra-low latency real-time word-by-word streaming injection with in-flight end trigger detection --- src/AppDelegate.swift | 8 ++++++++ src/SonioxClient.swift | 44 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/AppDelegate.swift b/src/AppDelegate.swift index 8afd321..4fd72f6 100644 --- a/src/AppDelegate.swift +++ b/src/AppDelegate.swift @@ -188,6 +188,14 @@ public final class AppDelegate: NSObject, NSApplicationDelegate { InputBoxManager.shared.streamDelta(text: delta) } + session.onEndTriggerDetected = { [weak self] in + guard let self = self else { return } + print("AppDelegate: In-flight end trigger ('and'/'تمام') detected. Stopping recording immediately...") + DispatchQueue.main.async { + self.stopRecording() + } + } + session.onFinalResult = { [weak self] result in DispatchQueue.main.async { guard let self = self else { return } diff --git a/src/SonioxClient.swift b/src/SonioxClient.swift index fba75eb..f357d14 100644 --- a/src/SonioxClient.swift +++ b/src/SonioxClient.swift @@ -23,10 +23,12 @@ public final class SonioxLiveSession { // Transcripts tracking private var committedFinalTokens: [String] = [] private var currentNonFinalTokens: [String] = [] + private var streamedPrefix: String = "" // Callbacks public var onPartialText: ((String) -> Void)? public var onCommittedDelta: ((String) -> Void)? + public var onEndTriggerDetected: (() -> Void)? public var onFinalResult: ((Result) -> Void)? public var onConnectionStateChanged: ((Bool) -> Void)? public var onNetworkDegraded: ((Bool) -> Void)? @@ -302,11 +304,47 @@ public final class SonioxLiveSession { let fullCommitted = committedFinalTokens.joined() let fullNonFinal = currentNonFinalTokens.joined() let combined = fullCommitted + fullNonFinal + + // Check for in-flight end trigger ("and", "تمام", "end") in combined text + var triggerDetected = false + let trimmedCombined = combined.trimmingCharacters(in: .whitespacesAndNewlines) + let currentWords = trimmedCombined.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty } + if let lastRaw = currentWords.last { + let cleanLast = lastRaw.replacingOccurrences(of: #"[.,!?:;،؛؟"'()\[\]{}«»–—\-]"#, with: "", options: .regularExpression).lowercased() + if cleanLast == "and" || cleanLast == "تمام" || cleanLast == "end" { + triggerDetected = true + } + } + + // Ultra-responsive real-time word-by-word streaming: + // A word in 'combined' is completed as soon as a whitespace character follows it. + var deltaToEmit: String? = nil + if !triggerDetected { + if let lastSpaceRange = combined.rangeOfCharacter(from: .whitespacesAndNewlines, options: .backwards) { + let stablePortion = String(combined[.. streamedPrefix.count { + if stablePortion.hasPrefix(streamedPrefix) { + let delta = String(stablePortion.dropFirst(streamedPrefix.count)) + streamedPrefix = stablePortion + deltaToEmit = delta + } else { + let common = stablePortion.commonPrefix(with: streamedPrefix) + if stablePortion.count > common.count { + let delta = String(stablePortion.dropFirst(common.count)) + streamedPrefix = stablePortion + deltaToEmit = delta + } + } + } + } + } lock.unlock() - // Emit streaming delta for newly committed words - if !newFinals.isEmpty { - let delta = newFinals.joined() + if triggerDetected { + DispatchQueue.main.async { [weak self] in + self?.onEndTriggerDetected?() + } + } else if let delta = deltaToEmit, !delta.isEmpty { DispatchQueue.main.async { [weak self] in self?.onCommittedDelta?(delta) }