diff --git a/VoiceInk/Services/ParakeetTranscriptionService.swift b/VoiceInk/Services/ParakeetTranscriptionService.swift index 71b6de7..cd0d315 100644 --- a/VoiceInk/Services/ParakeetTranscriptionService.swift +++ b/VoiceInk/Services/ParakeetTranscriptionService.swift @@ -124,6 +124,7 @@ class ParakeetTranscriptionService: TranscriptionService { } let result = try await asrManager.transcribe(speechAudio) + print(result.text) // Reset decoder state and cleanup after transcription to avoid blocking the transcription start Task { diff --git a/VoiceInk/Services/WordReplacementService.swift b/VoiceInk/Services/WordReplacementService.swift index 79aa210..09a72d4 100644 --- a/VoiceInk/Services/WordReplacementService.swift +++ b/VoiceInk/Services/WordReplacementService.swift @@ -14,24 +14,32 @@ class WordReplacementService { var modifiedText = text // Apply replacements (case-insensitive) - for (original, replacement) in replacements { - let usesBoundaries = usesWordBoundaries(for: original) + for (originalGroup, replacement) in replacements { + // Split comma-separated originals at apply time only + let variants = originalGroup + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } - if usesBoundaries { - // Word-boundary regex for full original string - let pattern = "\\b\(NSRegularExpression.escapedPattern(for: original))\\b" - if let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) { - let range = NSRange(modifiedText.startIndex..., in: modifiedText) - modifiedText = regex.stringByReplacingMatches( - in: modifiedText, - options: [], - range: range, - withTemplate: replacement - ) + for original in variants { + let usesBoundaries = usesWordBoundaries(for: original) + + if usesBoundaries { + // Word-boundary regex for full original string + let pattern = "\\b\(NSRegularExpression.escapedPattern(for: original))\\b" + if let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) { + let range = NSRange(modifiedText.startIndex..., in: modifiedText) + modifiedText = regex.stringByReplacingMatches( + in: modifiedText, + options: [], + range: range, + withTemplate: replacement + ) + } + } else { + // Fallback substring replace for non-spaced scripts + modifiedText = modifiedText.replacingOccurrences(of: original, with: replacement, options: .caseInsensitive) } - } else { - // Fallback substring replace for non-spaced scripts - modifiedText = modifiedText.replacingOccurrences(of: original, with: replacement, options: .caseInsensitive) } } diff --git a/VoiceInk/Views/Dictionary/EditReplacementSheet.swift b/VoiceInk/Views/Dictionary/EditReplacementSheet.swift index 50113b2..0ac4de8 100644 --- a/VoiceInk/Views/Dictionary/EditReplacementSheet.swift +++ b/VoiceInk/Views/Dictionary/EditReplacementSheet.swift @@ -23,7 +23,7 @@ struct EditReplacementSheet: View { Divider() formContent } - .frame(width: 460, height: 480) + .frame(width: 460, height: 560) } // MARK: – Subviews diff --git a/VoiceInk/Views/Dictionary/WordReplacementView.swift b/VoiceInk/Views/Dictionary/WordReplacementView.swift index a68ea4f..a8a6aa7 100644 --- a/VoiceInk/Views/Dictionary/WordReplacementView.swift +++ b/VoiceInk/Views/Dictionary/WordReplacementView.swift @@ -23,15 +23,10 @@ class WordReplacementManager: ObservableObject { } func addReplacement(original: String, replacement: String) { - // Support comma-separated originals mapping to the same replacement - let tokens = original - .split(separator: ",") - .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { !$0.isEmpty } - guard !tokens.isEmpty else { return } - for token in tokens { - replacements[token] = replacement - } + // Preserve comma-separated originals as a single entry + let trimmed = original.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + replacements[trimmed] = replacement } func removeReplacement(original: String) { @@ -39,18 +34,11 @@ class WordReplacementManager: ObservableObject { } func updateReplacement(oldOriginal: String, newOriginal: String, newReplacement: String) { - // Always remove the old key being edited + // Replace old key with the new comma-preserved key replacements.removeValue(forKey: oldOriginal) - - // Add one or more new keys (comma-separated) pointing to the same replacement - let tokens = newOriginal - .split(separator: ",") - .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { !$0.isEmpty } - guard !tokens.isEmpty else { return } - for token in tokens { - replacements[token] = newReplacement - } + let trimmed = newOriginal.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { return } + replacements[trimmed] = newReplacement } } @@ -272,10 +260,11 @@ struct AddReplacementSheet: View { // Example Section VStack(alignment: .leading, spacing: 8) { - Text("Example") + Text("Examples") .font(.subheadline) .foregroundColor(.secondary) + // Single original -> replacement HStack(spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text("Original:") @@ -297,6 +286,34 @@ struct AddReplacementSheet: View { .font(.callout) } } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(12) + .background(Color(.textBackgroundColor)) + .cornerRadius(8) + + // Comma-separated originals -> single replacement + HStack(spacing: 12) { + VStack(alignment: .leading, spacing: 4) { + Text("Original:") + .font(.caption) + .foregroundColor(.secondary) + Text("Voicing, Voice ink, Voiceing") + .font(.callout) + } + + Image(systemName: "arrow.right") + .font(.caption) + .foregroundColor(.secondary) + + VStack(alignment: .leading, spacing: 4) { + Text("Replacement:") + .font(.caption) + .foregroundColor(.secondary) + Text("VoiceInk") + .font(.callout) + } + } + .frame(maxWidth: .infinity, alignment: .leading) .padding(12) .background(Color(.textBackgroundColor)) .cornerRadius(8) @@ -307,7 +324,7 @@ struct AddReplacementSheet: View { .padding(.vertical) } } - .frame(width: 460, height: 480) + .frame(width: 460, height: 520) } private func addReplacement() {