Decouple merge model picker from the main chat's active provider

The merge model picker was reusing chatViewModel.availableModels,
which only ever holds whichever provider the main chat window
currently has active — fine for the main chat's own switcher (where
provider and model change together via the header), wrong for an
independent one-off picker like this. If your active chat was on
Anthropic, that's all you could pick from here regardless of what
other providers you have configured.

Added its own provider menu (mirroring HeaderView's) and an
independent model list fetched via ProviderRegistry for whichever
provider is selected, so OpenRouter, Anthropic, OpenAI, etc. are all
genuinely selectable regardless of what the main chat is doing.
This commit is contained in:
2026-07-28 14:57:19 +02:00
parent e898d4b6db
commit 37734232f5
@@ -25,7 +25,6 @@ import SwiftUI
struct CombineConversationsSheet: View {
@Environment(\.dismiss) var dismiss
@Environment(ChatViewModel.self) private var chatViewModel
let conversations: [Conversation]
var onCompleted: (Conversation) -> Void
@@ -37,6 +36,8 @@ struct CombineConversationsSheet: View {
@State private var errorMessage: String?
@State private var mergeModel: ModelInfo?
@State private var mergeProvider: Settings.Provider
@State private var mergeModels: [ModelInfo] = []
@State private var isLoadingMergeModels = false
@State private var showModelPicker = false
private let settings = SettingsService.shared
@@ -50,12 +51,9 @@ struct CombineConversationsSheet: View {
}
private var mergeModelLabel: String? {
if let mergeModel {
guard let mergeModel else { return nil }
return "\(mergeProvider.displayName) / \(mergeModel.name)"
}
guard let model = settings.defaultModel, !model.isEmpty else { return nil }
return "\(settings.defaultProvider.displayName) / \(model)"
}
private var isValid: Bool {
!name.trimmingCharacters(in: .whitespaces).isEmpty
@@ -125,12 +123,37 @@ struct CombineConversationsSheet: View {
Label("No model selected", systemImage: "exclamationmark.triangle.fill")
.font(.caption).foregroundStyle(.orange)
}
Button("Change…") {
Menu {
ForEach(ProviderRegistry.shared.configuredProviders, id: \.self) { p in
Button {
switchMergeProvider(to: p)
} label: {
HStack {
Image(systemName: p.iconName)
Text(p.displayName)
if p == mergeProvider { Image(systemName: "checkmark") }
}
}
}
} label: {
Text(mergeProvider.displayName)
}
.menuStyle(.borderlessButton)
.fixedSize()
.font(.caption)
.disabled(isProcessing || isLoadingMergeModels)
Button("Change Model…") {
showModelPicker = true
}
.buttonStyle(.link)
.font(.caption)
.disabled(isProcessing)
.disabled(isProcessing || isLoadingMergeModels || mergeModels.isEmpty)
if isLoadingMergeModels {
ProgressView().controlSize(.mini)
}
}
}
}
@@ -173,26 +196,46 @@ struct CombineConversationsSheet: View {
}
.frame(minWidth: 520, idealWidth: 560, minHeight: 460, idealHeight: 520)
.task {
if chatViewModel.availableModels.count <= 10 {
await chatViewModel.loadAvailableModels()
}
if mergeModel == nil, let defaultModel = settings.defaultModel {
mergeModel = chatViewModel.availableModels.first(where: { $0.id == defaultModel })
await loadMergeModels()
if let defaultModel = settings.defaultModel {
mergeModel = mergeModels.first(where: { $0.id == defaultModel })
}
}
.sheet(isPresented: $showModelPicker) {
ModelSelectorView(
models: chatViewModel.availableModels,
models: mergeModels,
selectedModel: mergeModel,
onSelect: { model in
mergeModel = model
mergeProvider = chatViewModel.inferProviderPublic(from: model.id) ?? mergeProvider
showModelPicker = false
}
)
}
}
private func switchMergeProvider(to newProvider: Settings.Provider) {
guard newProvider != mergeProvider else { return }
mergeProvider = newProvider
mergeModel = nil
mergeModels = []
Task { await loadMergeModels() }
}
private func loadMergeModels() async {
guard let provider = ProviderRegistry.shared.getProvider(for: mergeProvider) else {
mergeModels = []
return
}
isLoadingMergeModels = true
defer { isLoadingMergeModels = false }
do {
mergeModels = try await provider.listModels()
} catch {
Log.api.error("Failed to load models for merge provider \(mergeProvider.rawValue): \(error.localizedDescription)")
mergeModels = []
}
}
private func combine() {
isProcessing = true
errorMessage = nil