176 lines
6.8 KiB
Swift
176 lines
6.8 KiB
Swift
import Foundation
|
|
import os
|
|
|
|
class PromptDetectionService {
|
|
private let logger = Logger(
|
|
subsystem: "com.prakashjoshipax.VoiceInk",
|
|
category: "promptdetection"
|
|
)
|
|
|
|
struct PromptDetectionResult {
|
|
let shouldEnableAI: Bool
|
|
let selectedPromptId: UUID?
|
|
let processedText: String
|
|
let detectedTriggerWord: String?
|
|
let originalEnhancementState: Bool
|
|
let originalPromptId: UUID?
|
|
}
|
|
|
|
func analyzeText(_ text: String, with enhancementService: AIEnhancementService) -> PromptDetectionResult {
|
|
let originalEnhancementState = enhancementService.isEnhancementEnabled
|
|
let originalPromptId = enhancementService.selectedPromptId
|
|
|
|
if let selectedText = SelectedTextService.fetchSelectedText(), !selectedText.isEmpty {
|
|
return PromptDetectionResult(
|
|
shouldEnableAI: true,
|
|
selectedPromptId: PredefinedPrompts.assistantPromptId,
|
|
processedText: text, // The user's speech is the prompt for the selected text
|
|
detectedTriggerWord: nil,
|
|
originalEnhancementState: originalEnhancementState,
|
|
originalPromptId: originalPromptId
|
|
)
|
|
}
|
|
|
|
for prompt in enhancementService.allPrompts {
|
|
if !prompt.triggerWords.isEmpty {
|
|
if let (detectedWord, processedText) = findMatchingTriggerWord(from: text, triggerWords: prompt.triggerWords) {
|
|
return PromptDetectionResult(
|
|
shouldEnableAI: true,
|
|
selectedPromptId: prompt.id,
|
|
processedText: processedText,
|
|
detectedTriggerWord: detectedWord,
|
|
originalEnhancementState: originalEnhancementState,
|
|
originalPromptId: originalPromptId
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
return PromptDetectionResult(
|
|
shouldEnableAI: false,
|
|
selectedPromptId: nil,
|
|
processedText: text,
|
|
detectedTriggerWord: nil,
|
|
originalEnhancementState: originalEnhancementState,
|
|
originalPromptId: originalPromptId
|
|
)
|
|
}
|
|
|
|
func applyDetectionResult(_ result: PromptDetectionResult, to enhancementService: AIEnhancementService) async {
|
|
await MainActor.run {
|
|
if result.shouldEnableAI {
|
|
if !enhancementService.isEnhancementEnabled {
|
|
enhancementService.isEnhancementEnabled = true
|
|
}
|
|
if let promptId = result.selectedPromptId {
|
|
enhancementService.selectedPromptId = promptId
|
|
}
|
|
}
|
|
}
|
|
|
|
if result.shouldEnableAI {
|
|
try? await Task.sleep(nanoseconds: 50_000_000)
|
|
}
|
|
}
|
|
|
|
func restoreOriginalSettings(_ result: PromptDetectionResult, to enhancementService: AIEnhancementService) async {
|
|
if result.shouldEnableAI {
|
|
await MainActor.run {
|
|
if enhancementService.isEnhancementEnabled != result.originalEnhancementState {
|
|
enhancementService.isEnhancementEnabled = result.originalEnhancementState
|
|
}
|
|
if let originalId = result.originalPromptId, enhancementService.selectedPromptId != originalId {
|
|
enhancementService.selectedPromptId = originalId
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private func removeTriggerWord(from text: String, triggerWord: String) -> String? {
|
|
let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
let lowerText = trimmedText.lowercased()
|
|
let lowerTrigger = triggerWord.lowercased()
|
|
|
|
guard lowerText.hasPrefix(lowerTrigger) else { return nil }
|
|
|
|
let triggerEndIndex = trimmedText.index(trimmedText.startIndex, offsetBy: triggerWord.count)
|
|
|
|
if triggerEndIndex >= trimmedText.endIndex {
|
|
return ""
|
|
}
|
|
|
|
var remainingText = String(trimmedText[triggerEndIndex...])
|
|
|
|
remainingText = remainingText.replacingOccurrences(
|
|
of: "^[,\\.!\\?;:\\s]+",
|
|
with: "",
|
|
options: .regularExpression
|
|
)
|
|
|
|
remainingText = remainingText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
if !remainingText.isEmpty {
|
|
remainingText = remainingText.prefix(1).uppercased() + remainingText.dropFirst()
|
|
}
|
|
|
|
return remainingText
|
|
}
|
|
|
|
private func removeTrailingTriggerWord(from text: String, triggerWord: String) -> String? {
|
|
var trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
let punctuationSet = CharacterSet(charactersIn: ",.!?;:")
|
|
while let scalar = trimmedText.unicodeScalars.last, punctuationSet.contains(scalar) {
|
|
trimmedText.removeLast()
|
|
}
|
|
|
|
let lowerText = trimmedText.lowercased()
|
|
let lowerTrigger = triggerWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
guard lowerText.hasSuffix(lowerTrigger) else { return nil }
|
|
|
|
let triggerStartIndex = trimmedText.index(trimmedText.endIndex, offsetBy: -triggerWord.count)
|
|
if triggerStartIndex > trimmedText.startIndex {
|
|
let charBeforeTrigger = trimmedText[trimmedText.index(before: triggerStartIndex)]
|
|
if charBeforeTrigger.isLetter || charBeforeTrigger.isNumber {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var remainingText = String(trimmedText[..<triggerStartIndex])
|
|
|
|
remainingText = remainingText.replacingOccurrences(
|
|
of: "[,\\.!\\?;:\\s]+$",
|
|
with: "",
|
|
options: .regularExpression
|
|
)
|
|
remainingText = remainingText.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
|
|
if !remainingText.isEmpty {
|
|
remainingText = remainingText.prefix(1).uppercased() + remainingText.dropFirst()
|
|
}
|
|
|
|
return remainingText
|
|
}
|
|
|
|
private func findMatchingTriggerWord(from text: String, triggerWords: [String]) -> (String, String)? {
|
|
let trimmedWords = triggerWords.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
.filter { !$0.isEmpty }
|
|
|
|
// Sort by length (longest first) to match the most specific trigger word
|
|
let sortedTriggerWords = trimmedWords.sorted { $0.count > $1.count }
|
|
|
|
for triggerWord in sortedTriggerWords {
|
|
if let processedText = removeTrailingTriggerWord(from: text, triggerWord: triggerWord) {
|
|
return (triggerWord, processedText)
|
|
}
|
|
}
|
|
|
|
for triggerWord in sortedTriggerWords {
|
|
if let processedText = removeTriggerWord(from: text, triggerWord: triggerWord) {
|
|
return (triggerWord, processedText)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
} |