vOOice/VoiceInk/Services/PromptDetectionService.swift
2025-06-30 20:26:36 +05:45

179 lines
7.0 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 {
let wordCount = selectedText.split { $0.isWhitespace || $0.isNewline }.count
if wordCount >= 2 {
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
}
}