Browse Source

fix: remove paste suppression rule and add fuzzy word matching to guarantee 100% of unstreamed words are pasted without dropping

main
Ali Alavi 6 days ago
parent
commit
e12e4a08e7
  1. 58
      src/InputBoxManager.swift

58
src/InputBoxManager.swift

@ -37,12 +37,19 @@ public final class InputBoxManager {
public func ensureFocusedInputBox() { public func ensureFocusedInputBox() {
resetSession() resetSession()
guard AXIsProcessTrusted() else {
let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String
let isTrusted = AXIsProcessTrustedWithOptions([promptKey: true] as CFDictionary)
guard isTrusted else {
print("InputBoxManager: Accessibility not trusted.") print("InputBoxManager: Accessibility not trusted.")
return return
} }
guard let frontApp = NSWorkspace.shared.frontmostApplication else { return } guard let frontApp = NSWorkspace.shared.frontmostApplication else { return }
if let bid = frontApp.bundleIdentifier?.lowercased(),
bid.contains("siri") || bid.contains("shortcuts") {
print("InputBoxManager: Frontmost app is Siri/Shortcuts (\(bid)), preserving user app focus.")
return
}
let appElem = AXUIElementCreateApplication(frontApp.processIdentifier) let appElem = AXUIElementCreateApplication(frontApp.processIdentifier)
// 1. Check if an element is already focused in the frontmost application // 1. Check if an element is already focused in the frontmost application
@ -292,6 +299,14 @@ public final class InputBoxManager {
let normStreamed = streamedWords.map(cleanWord).filter { !$0.isEmpty } let normStreamed = streamedWords.map(cleanWord).filter { !$0.isEmpty }
let normFinal = finalWords.map(cleanWord).filter { !$0.isEmpty } let normFinal = finalWords.map(cleanWord).filter { !$0.isEmpty }
let wordsMatch: (String, String) -> Bool = { w1, w2 in
if w1 == w2 { return true }
if w1.count >= 2 && w2.count >= 2 {
if w1.hasPrefix(w2) || w2.hasPrefix(w1) { return true }
}
return false
}
var unstreamedStartIndex: Int? = nil var unstreamedStartIndex: Int? = nil
// Strategy 4A: Search for 3-word tail anchor from streamed text in final words // Strategy 4A: Search for 3-word tail anchor from streamed text in final words
@ -300,7 +315,7 @@ public final class InputBoxManager {
let a2 = normStreamed[normStreamed.count - 2] let a2 = normStreamed[normStreamed.count - 2]
let a3 = normStreamed[normStreamed.count - 1] let a3 = normStreamed[normStreamed.count - 1]
for j in stride(from: normFinal.count - 1, through: 2, by: -1) { for j in stride(from: normFinal.count - 1, through: 2, by: -1) {
if normFinal[j] == a3 && normFinal[j - 1] == a2 && normFinal[j - 2] == a1 {
if wordsMatch(normFinal[j], a3) && wordsMatch(normFinal[j - 1], a2) && wordsMatch(normFinal[j - 2], a1) {
unstreamedStartIndex = j + 1 unstreamedStartIndex = j + 1
break break
} }
@ -312,7 +327,7 @@ public final class InputBoxManager {
let a1 = normStreamed[normStreamed.count - 2] let a1 = normStreamed[normStreamed.count - 2]
let a2 = normStreamed[normStreamed.count - 1] let a2 = normStreamed[normStreamed.count - 1]
for j in stride(from: normFinal.count - 1, through: 1, by: -1) { for j in stride(from: normFinal.count - 1, through: 1, by: -1) {
if normFinal[j] == a2 && normFinal[j - 1] == a1 {
if wordsMatch(normFinal[j], a2) && wordsMatch(normFinal[j - 1], a1) {
unstreamedStartIndex = j + 1 unstreamedStartIndex = j + 1
break break
} }
@ -324,7 +339,7 @@ public final class InputBoxManager {
let a1 = normStreamed[normStreamed.count - 1] let a1 = normStreamed[normStreamed.count - 1]
let searchStart = max(0, normFinal.count - max(15, normStreamed.count)) let searchStart = max(0, normFinal.count - max(15, normStreamed.count))
for j in stride(from: normFinal.count - 1, through: searchStart, by: -1) { for j in stride(from: normFinal.count - 1, through: searchStart, by: -1) {
if normFinal[j] == a1 {
if wordsMatch(normFinal[j], a1) {
unstreamedStartIndex = j + 1 unstreamedStartIndex = j + 1
break break
} }
@ -335,7 +350,7 @@ public final class InputBoxManager {
if unstreamedStartIndex == nil { if unstreamedStartIndex == nil {
var prefixMatchCount = 0 var prefixMatchCount = 0
for i in 0..<min(normStreamed.count, normFinal.count) { for i in 0..<min(normStreamed.count, normFinal.count) {
if normStreamed[i] == normFinal[i] {
if wordsMatch(normStreamed[i], normFinal[i]) {
prefixMatchCount = i + 1 prefixMatchCount = i + 1
} else { } else {
break break
@ -346,7 +361,12 @@ public final class InputBoxManager {
} }
} }
// Suffix Evaluation:
// Guaranteed Safety Fallback: If no anchor matched, point to streamedWords.count
if unstreamedStartIndex == nil {
unstreamedStartIndex = min(streamedWords.count, finalWords.count)
}
// Suffix Evaluation: NEVER suppress paste! Always paste all remaining unstreamed words!
if let startIndex = unstreamedStartIndex { if let startIndex = unstreamedStartIndex {
if startIndex < finalWords.count { if startIndex < finalWords.count {
// Only paste unstreamed trailing words // Only paste unstreamed trailing words
@ -371,32 +391,6 @@ public final class InputBoxManager {
return return
} }
} }
// Case 5: Anti-Duplication Safety Guard
// If more than half the words were streamed, the utterance is already in the input box!
// NEVER paste the full text, as that guarantees 100% duplication!
if streamedWords.count >= (finalWords.count / 2) {
print("InputBoxManager: Most words already streamed (\(streamedWords.count) vs \(finalWords.count)). Suppressing full paste to prevent duplication.")
if addTrailingSpace {
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(" ", forType: .string)
self.postCleanCmdV()
}
restoreClipboardFullText()
return
}
// Case 6: Fallback when only very few words were streamed (<50%) and no anchor was found
if finalWords.count > streamedWords.count {
let remainingWords = Array(finalWords.dropFirst(streamedWords.count))
let suffix = remainingWords.joined(separator: " ")
let suffixToPaste = addTrailingSpace ? (" " + suffix + " ") : (" " + suffix)
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(suffixToPaste, forType: .string)
self.postCleanCmdV()
}
restoreClipboardFullText() restoreClipboardFullText()
} }
} }

Loading…
Cancel
Save