Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions TypeWhisper.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -4106,7 +4106,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.4.0;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.typewhisper.mac.dev;
PRODUCT_NAME = TypeWhisper;
SWIFT_OBJC_BRIDGING_HEADER = TypeWhisper/TypeWhisper-Bridging-Header.h;
Expand Down Expand Up @@ -4135,7 +4135,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.4.0;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.typewhisper.mac;
PRODUCT_NAME = TypeWhisper;
SWIFT_OBJC_BRIDGING_HEADER = TypeWhisper/TypeWhisper-Bridging-Header.h;
Expand All @@ -4151,7 +4151,7 @@
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
MARKETING_VERSION = 1.4.0;
MARKETING_VERSION = 1.5.0;
PRODUCT_NAME = "typewhisper-cli";
SWIFT_VERSION = 6.0;
};
Expand All @@ -4163,7 +4163,7 @@
CODE_SIGN_STYLE = Automatic;
DEAD_CODE_STRIPPING = YES;
ENABLE_HARDENED_RUNTIME = YES;
MARKETING_VERSION = 1.4.0;
MARKETING_VERSION = 1.5.0;
PRODUCT_NAME = "typewhisper-cli";
SWIFT_VERSION = 6.0;
};
Expand Down Expand Up @@ -4695,7 +4695,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MARKETING_VERSION = 1.4.0;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.typewhisper.mac.dev.widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand All @@ -4722,7 +4722,7 @@
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
MARKETING_VERSION = 1.4.0;
MARKETING_VERSION = 1.5.0;
PRODUCT_BUNDLE_IDENTIFIER = com.typewhisper.mac.widgets;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
Expand Down
90 changes: 76 additions & 14 deletions TypeWhisper/Services/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,16 @@ final class PluginManager: ObservableObject {
var llmProviders: [LLMProviderPlugin] {
loadedPlugins
.filter { $0.isEnabled }
.compactMap { $0.instance as? LLMProviderPlugin }
.flatMap { plugin -> [LLMProviderPlugin] in
var providers: [LLMProviderPlugin] = []
if let provider = plugin.instance as? LLMProviderPlugin {
providers.append(provider)
}
if let expanded = plugin.instance as? AdditionalLLMProvidersProviding {
providers.append(contentsOf: expanded.additionalLLMProviders)
}
return providers
}
}

var ttsProviders: [TTSProviderPlugin] {
Expand All @@ -275,7 +284,16 @@ final class PluginManager: ObservableObject {
var transcriptionEngines: [TranscriptionEnginePlugin] {
loadedPlugins
.filter { $0.isEnabled }
.compactMap { $0.instance as? TranscriptionEnginePlugin }
.flatMap { plugin -> [TranscriptionEnginePlugin] in
var engines: [TranscriptionEnginePlugin] = []
if let engine = plugin.instance as? TranscriptionEnginePlugin {
engines.append(engine)
}
if let expanded = plugin.instance as? AdditionalTranscriptionEnginesProviding {
engines.append(contentsOf: expanded.additionalTranscriptionEngines)
}
return engines
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

var actionPlugins: [ActionPlugin] {
Expand All @@ -296,8 +314,16 @@ final class PluginManager: ObservableObject {

func loadedTranscriptionPlugin(for providerId: String) -> LoadedPlugin? {
loadedPlugins.first {
guard let engine = $0.instance as? TranscriptionEnginePlugin else { return false }
return $0.isEnabled && engine.providerId == providerId
guard $0.isEnabled else { return false }
if let engine = $0.instance as? TranscriptionEnginePlugin,
engine.providerId == providerId {
return true
}
if let expanded = $0.instance as? AdditionalTranscriptionEnginesProviding,
expanded.additionalTranscriptionEngines.contains(where: { $0.providerId == providerId }) {
return true
}
return false
}
}

Expand All @@ -317,7 +343,22 @@ final class PluginManager: ObservableObject {
}

func llmProvider(for providerName: String) -> LLMProviderPlugin? {
llmProviders.first { $0.providerName.caseInsensitiveCompare(providerName) == .orderedSame }
let lookup = providerName.trimmingCharacters(in: .whitespacesAndNewlines)
guard !lookup.isEmpty else { return nil }

if let idMatch = llmProviders.first(where: {
$0.llmProviderId.caseInsensitiveCompare(lookup) == .orderedSame
}) {
return idMatch
}

return llmProviders.first { provider in
provider.llmProviderDisplayName.caseInsensitiveCompare(lookup) == .orderedSame
|| provider.providerName.caseInsensitiveCompare(lookup) == .orderedSame
|| provider.llmProviderLegacyAliases.contains {
$0.caseInsensitiveCompare(lookup) == .orderedSame
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

func isManifestCompatible(_ manifest: PluginManifest) -> Bool {
Expand Down Expand Up @@ -658,15 +699,8 @@ final class PluginManager: ObservableObject {
}
} else {
// If the deactivated plugin was selected as default engine, fall back to first available
if let engine = loadedPlugins[index].instance as? TranscriptionEnginePlugin {
let selectedProvider = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectedEngine)
if selectedProvider == engine.providerId {
let fallback = transcriptionEngines.first(where: { $0.providerId != engine.providerId && $0.isConfigured })
if let fallback {
ServiceContainer.shared.modelManagerService.selectProvider(fallback.providerId)
}
}
}
let disabledProviderIds = transcriptionProviderIds(exposedBy: loadedPlugins[index].instance)
selectFallbackTranscriptionProviderIfNeeded(disabling: disabledProviderIds)

let plugin = loadedPlugins[index]
if plugin.isRuntimeLoaded {
Expand All @@ -687,6 +721,34 @@ final class PluginManager: ObservableObject {
}
}

func transcriptionProviderIds(exposedBy pluginInstance: TypeWhisperPlugin) -> Set<String> {
var providerIds = Set<String>()
if let engine = pluginInstance as? TranscriptionEnginePlugin {
providerIds.insert(engine.providerId)
}
if let expanded = pluginInstance as? AdditionalTranscriptionEnginesProviding {
for engine in expanded.additionalTranscriptionEngines {
providerIds.insert(engine.providerId)
}
}
return providerIds
}

func selectFallbackTranscriptionProviderIfNeeded(disabling disabledProviderIds: Set<String>) {
guard let fallbackProviderId = fallbackTranscriptionProviderId(disabling: disabledProviderIds) else { return }
ServiceContainer.shared.modelManagerService.selectProvider(fallbackProviderId)
}

func fallbackTranscriptionProviderId(disabling disabledProviderIds: Set<String>) -> String? {
guard !disabledProviderIds.isEmpty,
let selectedProvider = UserDefaults.standard.string(forKey: UserDefaultsKeys.selectedEngine),
disabledProviderIds.contains(selectedProvider) else { return nil }

return transcriptionEngines.first {
!disabledProviderIds.contains($0.providerId) && $0.isConfigured
}?.providerId
}

func openPluginsFolder() {
NSWorkspace.shared.open(pluginsDirectory)
}
Expand Down
11 changes: 5 additions & 6 deletions TypeWhisper/Services/PromptProcessingService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class PromptProcessingService: ObservableObject {
}

for plugin in PluginManager.shared.llmProviders {
result.append((id: plugin.providerName, displayName: plugin.providerName))
result.append((id: plugin.llmProviderId, displayName: plugin.llmProviderDisplayName))
}

return result
Expand Down Expand Up @@ -108,15 +108,14 @@ class PromptProcessingService: ObservableObject {
if providerId == Self.appleIntelligenceId {
return "Apple Intelligence"
}
// Use the plugin's canonical providerName for display
return PluginManager.shared.llmProvider(for: providerId)?.providerName ?? providerId
return PluginManager.shared.llmProvider(for: providerId)?.llmProviderDisplayName ?? providerId
}

/// Normalize a provider ID to match the plugin's canonical providerName.
/// Handles migration from old enum rawValues ("groq") to plugin names ("Groq").
/// Normalize a provider ID to match the plugin's stable runtime ID.
/// Handles migration from old enum rawValues ("groq") to plugin IDs.
func normalizeProviderId(_ id: String) -> String {
if id == Self.appleIntelligenceId { return id }
return PluginManager.shared.llmProvider(for: id)?.providerName ?? id
return PluginManager.shared.llmProvider(for: id)?.llmProviderId ?? id
}

init() {
Expand Down
Loading