Move API keys from UserDefaults to secure Keychain storage. Add KeychainService and APIKeyManager for centralized key management. Enable iCloud Keychain sync for cross-device sharing between macOS and iOS.
199 lines
6.7 KiB
Swift
199 lines
6.7 KiB
Swift
import Foundation
|
|
|
|
// Enum to differentiate between model providers
|
|
enum ModelProvider: String, Codable, Hashable, CaseIterable {
|
|
case local = "Local"
|
|
case parakeet = "Parakeet"
|
|
case groq = "Groq"
|
|
case elevenLabs = "ElevenLabs"
|
|
case deepgram = "Deepgram"
|
|
case mistral = "Mistral"
|
|
case gemini = "Gemini"
|
|
case soniox = "Soniox"
|
|
case custom = "Custom"
|
|
case nativeApple = "Native Apple"
|
|
// Future providers can be added here
|
|
}
|
|
|
|
// A unified protocol for any transcription model
|
|
protocol TranscriptionModel: Identifiable, Hashable {
|
|
var id: UUID { get }
|
|
var name: String { get }
|
|
var displayName: String { get }
|
|
var description: String { get }
|
|
var provider: ModelProvider { get }
|
|
|
|
// Language capabilities
|
|
var isMultilingualModel: Bool { get }
|
|
var supportedLanguages: [String: String] { get }
|
|
}
|
|
|
|
extension TranscriptionModel {
|
|
func hash(into hasher: inout Hasher) {
|
|
hasher.combine(id)
|
|
}
|
|
|
|
var language: String {
|
|
isMultilingualModel ? "Multilingual" : "English-only"
|
|
}
|
|
}
|
|
|
|
// A new struct for Apple's native models
|
|
struct NativeAppleModel: TranscriptionModel {
|
|
let id = UUID()
|
|
let name: String
|
|
let displayName: String
|
|
let description: String
|
|
let provider: ModelProvider = .nativeApple
|
|
let isMultilingualModel: Bool
|
|
let supportedLanguages: [String: String]
|
|
}
|
|
|
|
// A new struct for Parakeet models
|
|
struct ParakeetModel: TranscriptionModel {
|
|
let id = UUID()
|
|
let name: String
|
|
let displayName: String
|
|
let description: String
|
|
let provider: ModelProvider = .parakeet
|
|
let size: String
|
|
let speed: Double
|
|
let accuracy: Double
|
|
let ramUsage: Double
|
|
var isMultilingualModel: Bool {
|
|
supportedLanguages.count > 1
|
|
}
|
|
let supportedLanguages: [String: String]
|
|
}
|
|
|
|
// A new struct for cloud models
|
|
struct CloudModel: TranscriptionModel {
|
|
let id: UUID
|
|
let name: String
|
|
let displayName: String
|
|
let description: String
|
|
let provider: ModelProvider
|
|
let speed: Double
|
|
let accuracy: Double
|
|
let isMultilingualModel: Bool
|
|
let supportedLanguages: [String: String]
|
|
|
|
init(id: UUID = UUID(), name: String, displayName: String, description: String, provider: ModelProvider, speed: Double, accuracy: Double, isMultilingual: Bool, supportedLanguages: [String: String]) {
|
|
self.id = id
|
|
self.name = name
|
|
self.displayName = displayName
|
|
self.description = description
|
|
self.provider = provider
|
|
self.speed = speed
|
|
self.accuracy = accuracy
|
|
self.isMultilingualModel = isMultilingual
|
|
self.supportedLanguages = supportedLanguages
|
|
}
|
|
}
|
|
|
|
/// Custom cloud model with API key stored in Keychain.
|
|
struct CustomCloudModel: TranscriptionModel, Codable {
|
|
let id: UUID
|
|
let name: String
|
|
let displayName: String
|
|
let description: String
|
|
let provider: ModelProvider = .custom
|
|
let apiEndpoint: String
|
|
let modelName: String
|
|
let isMultilingualModel: Bool
|
|
let supportedLanguages: [String: String]
|
|
|
|
/// API key retrieved from Keychain by model ID.
|
|
var apiKey: String {
|
|
APIKeyManager.shared.getCustomModelAPIKey(forModelId: id) ?? ""
|
|
}
|
|
|
|
init(id: UUID = UUID(), name: String, displayName: String, description: String, apiEndpoint: String, modelName: String, isMultilingual: Bool = true, supportedLanguages: [String: String]? = nil) {
|
|
self.id = id
|
|
self.name = name
|
|
self.displayName = displayName
|
|
self.description = description
|
|
self.apiEndpoint = apiEndpoint
|
|
self.modelName = modelName
|
|
self.isMultilingualModel = isMultilingual
|
|
self.supportedLanguages = supportedLanguages ?? PredefinedModels.getLanguageDictionary(isMultilingual: isMultilingual)
|
|
}
|
|
|
|
/// Custom Codable to migrate legacy apiKey from JSON to Keychain.
|
|
private enum CodingKeys: String, CodingKey {
|
|
case id, name, displayName, description, apiEndpoint, modelName, isMultilingualModel, supportedLanguages
|
|
case apiKey
|
|
}
|
|
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
id = try container.decode(UUID.self, forKey: .id)
|
|
name = try container.decode(String.self, forKey: .name)
|
|
displayName = try container.decode(String.self, forKey: .displayName)
|
|
description = try container.decode(String.self, forKey: .description)
|
|
apiEndpoint = try container.decode(String.self, forKey: .apiEndpoint)
|
|
modelName = try container.decode(String.self, forKey: .modelName)
|
|
isMultilingualModel = try container.decode(Bool.self, forKey: .isMultilingualModel)
|
|
supportedLanguages = try container.decode([String: String].self, forKey: .supportedLanguages)
|
|
|
|
if let legacyApiKey = try container.decodeIfPresent(String.self, forKey: .apiKey), !legacyApiKey.isEmpty {
|
|
APIKeyManager.shared.saveCustomModelAPIKey(legacyApiKey, forModelId: id)
|
|
}
|
|
}
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(id, forKey: .id)
|
|
try container.encode(name, forKey: .name)
|
|
try container.encode(displayName, forKey: .displayName)
|
|
try container.encode(description, forKey: .description)
|
|
try container.encode(apiEndpoint, forKey: .apiEndpoint)
|
|
try container.encode(modelName, forKey: .modelName)
|
|
try container.encode(isMultilingualModel, forKey: .isMultilingualModel)
|
|
try container.encode(supportedLanguages, forKey: .supportedLanguages)
|
|
}
|
|
}
|
|
|
|
struct LocalModel: TranscriptionModel {
|
|
let id = UUID()
|
|
let name: String
|
|
let displayName: String
|
|
let size: String
|
|
let supportedLanguages: [String: String]
|
|
let description: String
|
|
let speed: Double
|
|
let accuracy: Double
|
|
let ramUsage: Double
|
|
let provider: ModelProvider = .local
|
|
|
|
var downloadURL: String {
|
|
"https://huggingface.co/ggerganov/whisper.cpp/resolve/main/\(filename)"
|
|
}
|
|
|
|
var filename: String {
|
|
"\(name).bin"
|
|
}
|
|
|
|
var isMultilingualModel: Bool {
|
|
supportedLanguages.count > 1
|
|
}
|
|
}
|
|
|
|
// User-imported local models
|
|
struct ImportedLocalModel: TranscriptionModel {
|
|
let id = UUID()
|
|
let name: String
|
|
let displayName: String
|
|
let description: String
|
|
let provider: ModelProvider = .local
|
|
let isMultilingualModel: Bool
|
|
let supportedLanguages: [String: String]
|
|
|
|
init(fileBaseName: String) {
|
|
self.name = fileBaseName
|
|
self.displayName = fileBaseName
|
|
self.description = "Imported local model"
|
|
self.isMultilingualModel = true
|
|
self.supportedLanguages = PredefinedModels.getLanguageDictionary(isMultilingual: true, provider: .local)
|
|
}
|
|
} |