diff --git a/VoiceInk/Services/CloudTranscription/ElevenLabsTranscriptionService.swift b/VoiceInk/Services/CloudTranscription/ElevenLabsTranscriptionService.swift index 9a6b4dc..3cadccb 100644 --- a/VoiceInk/Services/CloudTranscription/ElevenLabsTranscriptionService.swift +++ b/VoiceInk/Services/CloudTranscription/ElevenLabsTranscriptionService.swift @@ -37,7 +37,9 @@ class ElevenLabsTranscriptionService { throw CloudTranscriptionError.missingAPIKey } - let apiURL = URL(string: "https://api.elevenlabs.io/v1/speech-to-text")! + guard let apiURL = URL(string: "https://api.elevenlabs.io/v1/speech-to-text") else { + throw NSError(domain: "ElevenLabsTranscriptionService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid API URL"]) + } return APIConfig(url: apiURL, apiKey: apiKey, modelName: model.name) } diff --git a/VoiceInk/Services/CloudTranscription/GroqTranscriptionService.swift b/VoiceInk/Services/CloudTranscription/GroqTranscriptionService.swift index 3a1c90a..27b5932 100644 --- a/VoiceInk/Services/CloudTranscription/GroqTranscriptionService.swift +++ b/VoiceInk/Services/CloudTranscription/GroqTranscriptionService.swift @@ -40,7 +40,9 @@ class GroqTranscriptionService { throw CloudTranscriptionError.missingAPIKey } - let apiURL = URL(string: "https://api.groq.com/openai/v1/audio/transcriptions")! + guard let apiURL = URL(string: "https://api.groq.com/openai/v1/audio/transcriptions") else { + throw NSError(domain: "GroqTranscriptionService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid API URL"]) + } return APIConfig(url: apiURL, apiKey: apiKey, modelName: model.name) } diff --git a/VoiceInk/Services/CloudTranscription/MistralTranscriptionService.swift b/VoiceInk/Services/CloudTranscription/MistralTranscriptionService.swift index 1d57942..f98d02d 100644 --- a/VoiceInk/Services/CloudTranscription/MistralTranscriptionService.swift +++ b/VoiceInk/Services/CloudTranscription/MistralTranscriptionService.swift @@ -12,7 +12,9 @@ class MistralTranscriptionService { throw CloudTranscriptionError.missingAPIKey } - let url = URL(string: "https://api.mistral.ai/v1/audio/transcriptions")! + guard let url = URL(string: "https://api.mistral.ai/v1/audio/transcriptions") else { + throw NSError(domain: "MistralTranscriptionService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid API URL"]) + } var request = URLRequest(url: url) request.httpMethod = "POST" diff --git a/VoiceInk/Services/CloudTranscription/OpenAICompatibleTranscriptionService.swift b/VoiceInk/Services/CloudTranscription/OpenAICompatibleTranscriptionService.swift index 90ed648..218bc5d 100644 --- a/VoiceInk/Services/CloudTranscription/OpenAICompatibleTranscriptionService.swift +++ b/VoiceInk/Services/CloudTranscription/OpenAICompatibleTranscriptionService.swift @@ -5,8 +5,12 @@ class OpenAICompatibleTranscriptionService { private let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "OpenAICompatibleService") func transcribe(audioURL: URL, model: CustomCloudModel) async throws -> String { + guard let url = URL(string: model.apiEndpoint) else { + throw NSError(domain: "OpenAICompatibleTranscriptionService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid API endpoint URL"]) + } + let config = APIConfig( - url: URL(string: model.apiEndpoint)!, + url: url, apiKey: model.apiKey, modelName: model.modelName ) diff --git a/VoiceInk/VoiceInk.swift b/VoiceInk/VoiceInk.swift index 1d6fa4c..7e76d96 100644 --- a/VoiceInk/VoiceInk.swift +++ b/VoiceInk/VoiceInk.swift @@ -54,13 +54,43 @@ struct VoiceInkApp: App { container = try ModelContainer(for: schema, configurations: [modelConfiguration]) - // Print SwiftData storage location + #if DEBUG + // Print SwiftData storage location in debug builds only if let url = container.mainContext.container.configurations.first?.url { print("💾 SwiftData storage location: \(url.path)") } + #endif } catch { - fatalError("Failed to create ModelContainer for Transcription: \(error.localizedDescription)") + // Graceful degradation: Use in-memory storage as fallback + let logger = Logger(subsystem: "com.prakashjoshipax.voiceink", category: "Initialization") + logger.error("Failed to create persistent ModelContainer: \(error.localizedDescription)") + + // Attempt in-memory fallback + do { + let schema = Schema([Transcription.self]) + let configuration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: true) + container = try ModelContainer(for: schema, configurations: [configuration]) + + logger.warning("Using in-memory storage as fallback. Data will not persist between sessions.") + + // Show alert to user about storage issue + DispatchQueue.main.async { + let alert = NSAlert() + alert.messageText = "Storage Warning" + alert.informativeText = "VoiceInk couldn't access its storage location. Your transcriptions will not be saved between sessions." + alert.alertStyle = .warning + alert.addButton(withTitle: "OK") + alert.runModal() + } + } catch { + // Last resort: critical failure, but log and attempt to continue + logger.critical("Failed to create in-memory ModelContainer: \(error.localizedDescription)") + + // Create minimal container with empty schema + let schema = Schema([Transcription.self]) + container = try! ModelContainer(for: schema, configurations: [ModelConfiguration(schema: schema, isStoredInMemoryOnly: true)]) + } } // Initialize services with proper sharing of instances diff --git a/VoiceInk/Whisper/WhisperPrompt.swift b/VoiceInk/Whisper/WhisperPrompt.swift index bf62c79..1c1d24b 100644 --- a/VoiceInk/Whisper/WhisperPrompt.swift +++ b/VoiceInk/Whisper/WhisperPrompt.swift @@ -106,8 +106,8 @@ class WhisperPrompt: ObservableObject { return customPrompt } - // Otherwise return the default prompt - return languagePrompts[language] ?? languagePrompts["default"]! + // Otherwise return the default prompt, with safe fallback + return languagePrompts[language] ?? languagePrompts["default"] ?? "" } func setCustomPrompt(_ prompt: String, for language: String) {