gsd-pi 0.3.3 → 2.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,15 +45,93 @@ do {
45
45
  exit(1)
46
46
  }
47
47
 
48
- var lastText = ""
48
+ // Accumulated finalized text from previous recognition segments.
49
+ // On-device recognition (especially macOS/iOS 18+) can reset
50
+ // bestTranscription.formattedString after a pause, discarding
51
+ // previous text. We detect this by tracking the last known good
52
+ // text and noticing when the new text is shorter / doesn't start
53
+ // with the previous text. When that happens we treat the previous
54
+ // text as finalized and start accumulating the new segment on top.
55
+ var accumulated = ""
56
+ var lastPartialText = ""
57
+ var lastEmitted = ""
49
58
 
50
59
  recognizer.recognitionTask(with: request) { result, error in
51
60
  if let result = result {
52
61
  let text = result.bestTranscription.formattedString
53
- if text != lastText {
54
- lastText = text
55
- let prefix = result.isFinal ? "FINAL" : "PARTIAL"
56
- print("\(prefix):\(text)")
62
+
63
+ if result.isFinal {
64
+ // True final from the recognizer commit everything
65
+ let full: String
66
+ // Check if the final text already includes accumulated content
67
+ // (some OS versions give cumulative finals, others reset)
68
+ if !accumulated.isEmpty && !text.lowercased().hasPrefix(accumulated.lowercased()) {
69
+ full = accumulated + " " + text
70
+ } else if !accumulated.isEmpty && text.count < accumulated.count {
71
+ // Final is shorter than what we accumulated — use accumulated + new
72
+ full = accumulated + " " + text
73
+ } else {
74
+ full = text
75
+ }
76
+ accumulated = ""
77
+ lastPartialText = ""
78
+ if full != lastEmitted {
79
+ lastEmitted = full
80
+ print("FINAL:\(full)")
81
+ }
82
+ return
83
+ }
84
+
85
+ // Detect transcription reset: if the new partial text is significantly
86
+ // shorter than what we had, or doesn't start with the previous text,
87
+ // the recognizer has reset after a pause. Finalize what we had.
88
+ let prevText = lastPartialText
89
+ if !prevText.isEmpty && !text.isEmpty {
90
+ let prevWords = prevText.split(separator: " ")
91
+ let newWords = text.split(separator: " ")
92
+
93
+ // Reset detection: new text has fewer words than previous AND
94
+ // the first few words don't match (i.e. it's truly new speech,
95
+ // not just the recognizer revising the last word)
96
+ let looksLikeReset: Bool
97
+ if newWords.count < prevWords.count / 2 {
98
+ // Significant drop in word count — likely a reset
99
+ looksLikeReset = true
100
+ } else if newWords.count < prevWords.count &&
101
+ !prevWords.isEmpty && !newWords.isEmpty &&
102
+ newWords[0] != prevWords[0] {
103
+ // Different starting word + fewer words — reset
104
+ looksLikeReset = true
105
+ } else {
106
+ looksLikeReset = false
107
+ }
108
+
109
+ if looksLikeReset {
110
+ // Commit the previous partial text to accumulated
111
+ if accumulated.isEmpty {
112
+ accumulated = prevText
113
+ } else {
114
+ accumulated = accumulated + " " + prevText
115
+ }
116
+ // Emit a FINAL for the committed text so the TS side updates
117
+ print("FINAL:\(accumulated)")
118
+ lastEmitted = accumulated
119
+ }
120
+ }
121
+
122
+ lastPartialText = text
123
+
124
+ // Build the full display text
125
+ let displayText: String
126
+ if accumulated.isEmpty {
127
+ displayText = text
128
+ } else {
129
+ displayText = accumulated + " " + text
130
+ }
131
+
132
+ if displayText != lastEmitted {
133
+ lastEmitted = displayText
134
+ print("PARTIAL:\(displayText)")
57
135
  }
58
136
  }
59
137
  if let error = error {