diff --git a/VoiceInk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/VoiceInk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 06849e9..ad0d30f 100644 --- a/VoiceInk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/VoiceInk.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -16,7 +16,7 @@ "location" : "https://github.com/FluidInference/FluidAudio", "state" : { "branch" : "main", - "revision" : "8136bd0642e7c5ce1f6f5b2931890266aeecb08c" + "revision" : "ddee663c4a9806d4f139943b0978b0f0a961587b" } }, { diff --git a/VoiceInk/Services/ParakeetTranscriptionService.swift b/VoiceInk/Services/ParakeetTranscriptionService.swift index 0c0b2f0..82927a5 100644 --- a/VoiceInk/Services/ParakeetTranscriptionService.swift +++ b/VoiceInk/Services/ParakeetTranscriptionService.swift @@ -4,6 +4,17 @@ import AVFoundation import FluidAudio import os.log +enum ParakeetTranscriptionError: LocalizedError { + case modelValidationFailed(String) + + var errorDescription: String? { + switch self { + case .modelValidationFailed(let message): + return message + } + } +} + class ParakeetTranscriptionService: TranscriptionService { private var asrManager: AsrManager? private var vadManager: VadManager? @@ -21,6 +32,14 @@ class ParakeetTranscriptionService: TranscriptionService { cleanup() + // Validate models before loading + let isValid = try await AsrModels.isModelValid(version: version) + + if !isValid { + logger.error("Model validation failed for \(version == .v2 ? "v2" : "v3"). Models are corrupted.") + throw ParakeetTranscriptionError.modelValidationFailed("Parakeet models are corrupted. Please delete and re-download the model.") + } + let manager = AsrManager(config: .default) let models = try await AsrModels.loadFromCache( configuration: nil, diff --git a/VoiceInk/Services/SystemArchitecture.swift b/VoiceInk/Services/SystemArchitecture.swift new file mode 100644 index 0000000..cf0054d --- /dev/null +++ b/VoiceInk/Services/SystemArchitecture.swift @@ -0,0 +1,29 @@ +import Foundation + +enum SystemArchitecture { + static var isIntelMac: Bool { + #if arch(x86_64) + return true + #else + return false + #endif + } + + static var isAppleSilicon: Bool { + #if arch(arm64) + return true + #else + return false + #endif + } + + static var current: String { + #if arch(arm64) + return "Apple Silicon (ARM64)" + #elseif arch(x86_64) + return "Intel (x86_64)" + #else + return "Unknown" + #endif + } +} diff --git a/VoiceInk/Services/SystemInfoService.swift b/VoiceInk/Services/SystemInfoService.swift index 5ecfb05..490c2b4 100644 --- a/VoiceInk/Services/SystemInfoService.swift +++ b/VoiceInk/Services/SystemInfoService.swift @@ -88,13 +88,7 @@ class SystemInfoService { } private func getArchitecture() -> String { - #if arch(x86_64) - return "Intel x86_64" - #elseif arch(arm64) - return "Apple Silicon (ARM64)" - #else - return "Unknown" - #endif + return SystemArchitecture.current } private func getAudioInputMode() -> String { diff --git a/VoiceInk/Views/AI Models/ModelManagementView.swift b/VoiceInk/Views/AI Models/ModelManagementView.swift index 001f80e..0db0fae 100644 --- a/VoiceInk/Views/AI Models/ModelManagementView.swift +++ b/VoiceInk/Views/AI Models/ModelManagementView.swift @@ -33,6 +33,10 @@ struct ModelManagementView: View { var body: some View { ScrollView { VStack(alignment: .leading, spacing: 24) { + if SystemArchitecture.isIntelMac { + intelMacWarningBanner + } + defaultModelSection languageSelectionSection availableModelsSection @@ -209,6 +213,43 @@ struct ModelManagementView: View { .padding() } + private var intelMacWarningBanner: some View { + HStack(spacing: 10) { + Image(systemName: "exclamationmark.triangle.fill") + .font(.system(size: 14, weight: .semibold)) + .foregroundColor(.orange) + + Text("Local models don't work reliably on Intel Macs") + .font(.system(size: 13, weight: .medium)) + .foregroundColor(.primary.opacity(0.85)) + + Spacer() + + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + selectedFilter = .cloud + } + }) { + HStack(spacing: 4) { + Text("Use Cloud") + .font(.system(size: 12, weight: .semibold)) + Image(systemName: "arrow.right") + .font(.system(size: 10, weight: .bold)) + } + .foregroundColor(.orange) + .padding(.horizontal, 12) + .padding(.vertical, 6) + .background(Color.orange.opacity(0.12)) + .cornerRadius(6) + } + .buttonStyle(.plain) + } + .padding(.horizontal, 14) + .padding(.vertical, 10) + .background(Color.orange.opacity(0.08)) + .cornerRadius(8) + } + private var filteredModels: [any TranscriptionModel] { switch selectedFilter { case .recommended: