
















@@ -37,6 +37,7 @@ actor VoiceWakeRuntime {
3737private var listeningState: ListeningState = .idle
3838private var overlayToken: UUID?
3939private var activeTriggerEndTime: TimeInterval?
40+private var activeTriggerWord: String?
4041private var scheduledRestartTask: Task<Void, Never>?
4142private var lastLoggedText: String?
4243private var lastLoggedAt: Date?
@@ -256,6 +257,7 @@ actor VoiceWakeRuntime {
256257self.currentConfig = nil
257258self.listeningState = .idle
258259self.activeTriggerEndTime = nil
260+self.activeTriggerWord = nil
259261self.logger.debug("voicewake runtime stopped")
260262DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "stopped")
261263@@ -366,7 +368,11 @@ actor VoiceWakeRuntime {
366368} else {
367369self.logger.info("voicewake runtime detected len=\(match.command.count)")
368370}
369-await self.beginCapture(command: match.command, triggerEndTime: match.triggerEndTime, config: config)
371+await self.beginCapture(
372+ command: match.command,
373+ triggerEndTime: match.triggerEndTime,
374+ triggerWord: match.trigger,
375+ config: config)
370376} else if !transcript.isEmpty, update.error == nil {
371377if self.isTriggerOnly(transcript: transcript, triggers: config.triggers) {
372378self.preDetectTask?.cancel()
@@ -494,13 +500,33 @@ actor VoiceWakeRuntime {
494500return
495501}
496502self.logger.info("voicewake runtime detected (trigger-only pause)")
497-await self.beginCapture(command: "", triggerEndTime: nil, config: config)
503+let matchedTrigger = self.matchedTriggerWord(transcript: lastText, triggers: triggers)
504+await self.beginCapture(
505+ command: "",
506+ triggerEndTime: nil,
507+ triggerWord: matchedTrigger,
508+ config: config)
498509}
499510500511private func isTriggerOnly(transcript: String, triggers: [String]) -> Bool {
512+Self.isTriggerOnlyText(transcript: transcript, triggers: triggers)
513+}
514+515+private func matchedTriggerWord(transcript: String, triggers: [String]) -> String? {
516+Self.matchedTriggerWordText(transcript: transcript, triggers: triggers)
517+}
518+519+private static func isTriggerOnlyText(transcript: String, triggers: [String]) -> Bool {
501520guard WakeWordGate.matchesTextOnly(text: transcript, triggers: triggers) else { return false }
502-guard VoiceWakeTextUtils.startsWithTrigger(transcript: transcript, triggers: triggers) else { return false }
503-return Self.trimmedAfterTrigger(transcript, triggers: triggers).isEmpty
521+guard
522+VoiceWakeTextUtils.startsWithTrigger(transcript: transcript, triggers: triggers)
523+ || VoiceWakeTextUtils.hasOnlyFillerBeforeTrigger(transcript: transcript, triggers: triggers)
524+else { return false }
525+return self.trimmedAfterTrigger(transcript, triggers: triggers).isEmpty
526+}
527+528+private static func matchedTriggerWordText(transcript: String, triggers: [String]) -> String? {
529+VoiceWakeTextUtils.matchedTriggerWord(transcript: transcript, triggers: triggers)
504530}
505531506532private func preDetectSilenceCheck(
@@ -527,10 +553,16 @@ actor VoiceWakeRuntime {
527553await self.beginCapture(
528554 command: match.command,
529555 triggerEndTime: match.triggerEndTime,
556+ triggerWord: match.trigger,
530557 config: config)
531558}
532559533-private func beginCapture(command: String, triggerEndTime: TimeInterval?, config: RuntimeConfig) async {
560+private func beginCapture(
561+ command: String,
562+ triggerEndTime: TimeInterval?,
563+ triggerWord: String?,
564+ config: RuntimeConfig) async
565+{
534566 // When "Trigger Talk Mode" is enabled, skip the capture/overlay flow entirely
535567 // and activate Talk Mode immediately. Talk Mode handles its own STT pipeline.
536568 // Pause the wake listener to avoid two audio pipelines competing on the mic
@@ -545,7 +577,6 @@ actor VoiceWakeRuntime {
545577await AppStateStore.shared.setTalkEnabled(true)
546578return
547579}
548-549580self.listeningState = .voiceWake
550581self.isCapturing = true
551582DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "beginCapture")
@@ -557,6 +588,7 @@ actor VoiceWakeRuntime {
557588self.heardBeyondTrigger = !command.isEmpty
558589self.triggerChimePlayed = false
559590self.activeTriggerEndTime = triggerEndTime
591+self.activeTriggerWord = triggerWord
560592self.preDetectTask?.cancel()
561593self.preDetectTask = nil
562594self.triggerOnlyTask?.cancel()
@@ -577,7 +609,8 @@ actor VoiceWakeRuntime {
577609 source: .wakeWord,
578610 text: snapshot,
579611 attributed: attributed,
580- forwardEnabled: true)
612+ forwardEnabled: true,
613+ voiceWakeTrigger: triggerWord)
581614}
582615583616 // Keep the "ears" boosted for the capture window so the status icon animates while recording.
@@ -632,7 +665,9 @@ actor VoiceWakeRuntime {
632665self.lastHeard = nil
633666self.heardBeyondTrigger = false
634667self.triggerChimePlayed = false
668+let triggerWord = self.activeTriggerWord
635669self.activeTriggerEndTime = nil
670+self.activeTriggerWord = nil
636671self.lastTranscript = nil
637672self.lastTranscriptAt = nil
638673self.preDetectTask?.cancel()
@@ -653,14 +688,17 @@ actor VoiceWakeRuntime {
653688 token: token,
654689 text: finalTranscript,
655690 sendChime: sendChime,
656- autoSendAfter: delay)
691+ autoSendAfter: delay,
692+ voiceWakeTrigger: triggerWord)
657693}
658694} else if !finalTranscript.isEmpty {
659695if sendChime != .none {
660696await MainActor.run { VoiceWakeChimePlayer.play(sendChime, reason: "voicewake.send") }
661697}
662698Task.detached {
663-await VoiceWakeForwarder.forward(transcript: finalTranscript)
699+await VoiceWakeForwarder.forward(
700+ transcript: finalTranscript,
701+ options: .init(voiceWakeTrigger: triggerWord))
664702}
665703}
666704self.overlayToken = nil
@@ -784,6 +822,14 @@ actor VoiceWakeRuntime {
784822 !self.trimmedAfterTrigger(text, triggers: triggers).isEmpty
785823}
786824825+static func _testIsTriggerOnly(_ text: String, triggers: [String]) -> Bool {
826+self.isTriggerOnlyText(transcript: text, triggers: triggers)
827+}
828+829+static func _testMatchedTriggerWord(_ text: String, triggers: [String]) -> String? {
830+self.matchedTriggerWordText(transcript: text, triggers: triggers)
831+}
832+787833static func _testAttributedColor(isFinal: Bool) -> NSColor {
788834VoiceOverlayTextFormatting.makeAttributed(committed: "sample", volatile: "", isFinal: isFinal)
789835.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor ?? .clear
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。