diff --git a/VoiceInk/LibWhisper.swift b/VoiceInk/LibWhisper.swift index 564918d..4f07c0a 100644 --- a/VoiceInk/LibWhisper.swift +++ b/VoiceInk/LibWhisper.swift @@ -29,6 +29,9 @@ actor WhisperContext { func fullTranscribe(samples: [Float]) { guard let context = context else { return } + // Capture prompt state at start + let currentPrompt = prompt + // Leave 2 processors free (i.e. the high-efficiency cores). let maxThreads = max(1, min(8, cpuCount() - 2)) print("Selecting \(maxThreads) threads") @@ -49,12 +52,12 @@ actor WhisperContext { } // Only use prompt for English language - if selectedLanguage == "en" && prompt != nil { - promptCString = Array(prompt!.utf8CString) + if selectedLanguage == "en" && currentPrompt != nil { + promptCString = Array(currentPrompt!.utf8CString) params.initial_prompt = promptCString?.withUnsafeBufferPointer { ptr in ptr.baseAddress } - print("Using prompt for English transcription: \(prompt!)") + print("Using prompt for English transcription: \(currentPrompt!)") } else { promptCString = nil params.initial_prompt = nil diff --git a/VoiceInk/Services/AIPrompts.swift b/VoiceInk/Services/AIPrompts.swift index 1ce9c77..1fb3b26 100644 --- a/VoiceInk/Services/AIPrompts.swift +++ b/VoiceInk/Services/AIPrompts.swift @@ -69,7 +69,8 @@ enum AIPrompts { IMPORTANT: Only apply replacements if specific words are provided - Skip any replacement activity if no replacement options are available - When replacements are provided: - - Replace specified words or phrases exactly as provided + - Replace ONLY exact matches of the specified words/phrases + - Do NOT replace partial matches or similar words - Apply replacements before other enhancements - Maintain case sensitivity when applying replacements - Preserve the flow and readability of the text diff --git a/VoiceInk/Views/Dictionary/DictionarySettingsView.swift b/VoiceInk/Views/Dictionary/DictionarySettingsView.swift index 5813e30..d330499 100644 --- a/VoiceInk/Views/Dictionary/DictionarySettingsView.swift +++ b/VoiceInk/Views/Dictionary/DictionarySettingsView.swift @@ -2,6 +2,7 @@ import SwiftUI struct DictionarySettingsView: View { @State private var selectedSection: DictionarySection = .spellings + @EnvironmentObject private var whisperState: WhisperState enum DictionarySection: String, CaseIterable { case spellings = "Correct Spellings" @@ -93,7 +94,7 @@ struct DictionarySettingsView: View { VStack(alignment: .leading, spacing: 20) { switch selectedSection { case .spellings: - DictionaryView() + DictionaryView(whisperState: whisperState) .background(Color(.windowBackgroundColor).opacity(0.4)) .cornerRadius(10) case .replacements: diff --git a/VoiceInk/Views/Dictionary/DictionaryView.swift b/VoiceInk/Views/Dictionary/DictionaryView.swift index f511c4e..cf12cac 100644 --- a/VoiceInk/Views/Dictionary/DictionaryView.swift +++ b/VoiceInk/Views/Dictionary/DictionaryView.swift @@ -33,9 +33,10 @@ struct DictionaryItem: Identifiable, Hashable, Codable { class DictionaryManager: ObservableObject { @Published var items: [DictionaryItem] = [] private let saveKey = "CustomDictionaryItems" - var onDictionaryChanged: (([String]) -> Void)? + @Published var whisperState: WhisperState - init() { + init(whisperState: WhisperState) { + self.whisperState = whisperState loadItems() } @@ -53,22 +54,20 @@ class DictionaryManager: ObservableObject { saveItems() } } - notifyDictionaryChanged() + Task { @MainActor in + await whisperState.saveDictionaryItems(items) + } } private func saveItems() { if let encoded = try? JSONEncoder().encode(items) { UserDefaults.standard.set(encoded, forKey: saveKey) - notifyDictionaryChanged() + Task { @MainActor in + await whisperState.saveDictionaryItems(items) + } } } - private func notifyDictionaryChanged() { - // Only include enabled words in the dictionary - let enabledWords = items.filter { $0.isEnabled }.map { $0.word } - onDictionaryChanged?(enabledWords) - } - func addWord(_ word: String) { let normalizedWord = word.trimmingCharacters(in: .whitespacesAndNewlines) guard !items.contains(where: { $0.word.lowercased() == normalizedWord.lowercased() }) else { @@ -98,12 +97,16 @@ class DictionaryManager: ObservableObject { } struct DictionaryView: View { - @StateObject private var dictionaryManager = DictionaryManager() + @StateObject private var dictionaryManager: DictionaryManager @EnvironmentObject private var whisperState: WhisperState @State private var newWord = "" @State private var showAlert = false @State private var alertMessage = "" + init(whisperState: WhisperState) { + _dictionaryManager = StateObject(wrappedValue: DictionaryManager(whisperState: whisperState)) + } + var body: some View { VStack(alignment: .leading, spacing: 20) { // Information Section @@ -177,10 +180,6 @@ struct DictionaryView: View { Text(alertMessage) } .onAppear { - dictionaryManager.onDictionaryChanged = { words in - whisperState.updateDictionaryWords(words) - } - // Initial update whisperState.updateDictionaryWords(dictionaryManager.allWords) } } diff --git a/VoiceInk/WhisperState.swift b/VoiceInk/WhisperState.swift index 81c4d2d..131b975 100644 --- a/VoiceInk/WhisperState.swift +++ b/VoiceInk/WhisperState.swift @@ -89,6 +89,7 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate { private let recorder = Recorder() private var recordedFile: URL? = nil private var dictionaryWords: [String] = [] + private let saveKey = "CustomDictionaryItems" let modelContext: ModelContext @@ -150,6 +151,7 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate { createModelsDirectoryIfNeeded() createRecordingsDirectoryIfNeeded() loadAvailableModels() + loadDictionaryItems() // Load saved model if let savedModelName = UserDefaults.standard.string(forKey: "CurrentModel"), @@ -193,6 +195,17 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate { } } + private func loadDictionaryItems() { + guard let data = UserDefaults.standard.data(forKey: saveKey) else { return } + + // Try loading with new format first + if let savedItems = try? JSONDecoder().decode([DictionaryItem].self, from: data) { + let enabledWords = savedItems.filter { $0.isEnabled }.map { $0.word } + dictionaryWords = enabledWords + updateTranscriptionPrompt() + } + } + // Modify loadModel to be private and async private func loadModel(_ model: WhisperModel) async throws { guard whisperContext == nil else { return } // Model already loaded @@ -828,6 +841,15 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate { return permanentURL } + + func saveDictionaryItems(_ items: [DictionaryItem]) async { + if let encoded = try? JSONEncoder().encode(items) { + UserDefaults.standard.set(encoded, forKey: saveKey) + let enabledWords = items.filter { $0.isEnabled }.map { $0.word } + dictionaryWords = enabledWords + updateTranscriptionPrompt() + } + } } struct WhisperModel: Identifiable {