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:
@@ -25,7 +25,6 @@ import SwiftUI
|
|||||||
|
|
||||||
struct CombineConversationsSheet: View {
|
struct CombineConversationsSheet: View {
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
@Environment(ChatViewModel.self) private var chatViewModel
|
|
||||||
|
|
||||||
let conversations: [Conversation]
|
let conversations: [Conversation]
|
||||||
var onCompleted: (Conversation) -> Void
|
var onCompleted: (Conversation) -> Void
|
||||||
@@ -37,6 +36,8 @@ struct CombineConversationsSheet: View {
|
|||||||
@State private var errorMessage: String?
|
@State private var errorMessage: String?
|
||||||
@State private var mergeModel: ModelInfo?
|
@State private var mergeModel: ModelInfo?
|
||||||
@State private var mergeProvider: Settings.Provider
|
@State private var mergeProvider: Settings.Provider
|
||||||
|
@State private var mergeModels: [ModelInfo] = []
|
||||||
|
@State private var isLoadingMergeModels = false
|
||||||
@State private var showModelPicker = false
|
@State private var showModelPicker = false
|
||||||
|
|
||||||
private let settings = SettingsService.shared
|
private let settings = SettingsService.shared
|
||||||
@@ -50,11 +51,8 @@ struct CombineConversationsSheet: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var mergeModelLabel: String? {
|
private var mergeModelLabel: String? {
|
||||||
if let mergeModel {
|
guard let mergeModel else { return nil }
|
||||||
return "\(mergeProvider.displayName) / \(mergeModel.name)"
|
return "\(mergeProvider.displayName) / \(mergeModel.name)"
|
||||||
}
|
|
||||||
guard let model = settings.defaultModel, !model.isEmpty else { return nil }
|
|
||||||
return "\(settings.defaultProvider.displayName) / \(model)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isValid: Bool {
|
private var isValid: Bool {
|
||||||
@@ -125,12 +123,37 @@ struct CombineConversationsSheet: View {
|
|||||||
Label("No model selected", systemImage: "exclamationmark.triangle.fill")
|
Label("No model selected", systemImage: "exclamationmark.triangle.fill")
|
||||||
.font(.caption).foregroundStyle(.orange)
|
.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
|
showModelPicker = true
|
||||||
}
|
}
|
||||||
.buttonStyle(.link)
|
.buttonStyle(.link)
|
||||||
.font(.caption)
|
.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)
|
.frame(minWidth: 520, idealWidth: 560, minHeight: 460, idealHeight: 520)
|
||||||
.task {
|
.task {
|
||||||
if chatViewModel.availableModels.count <= 10 {
|
await loadMergeModels()
|
||||||
await chatViewModel.loadAvailableModels()
|
if let defaultModel = settings.defaultModel {
|
||||||
}
|
mergeModel = mergeModels.first(where: { $0.id == defaultModel })
|
||||||
if mergeModel == nil, let defaultModel = settings.defaultModel {
|
|
||||||
mergeModel = chatViewModel.availableModels.first(where: { $0.id == defaultModel })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showModelPicker) {
|
.sheet(isPresented: $showModelPicker) {
|
||||||
ModelSelectorView(
|
ModelSelectorView(
|
||||||
models: chatViewModel.availableModels,
|
models: mergeModels,
|
||||||
selectedModel: mergeModel,
|
selectedModel: mergeModel,
|
||||||
onSelect: { model in
|
onSelect: { model in
|
||||||
mergeModel = model
|
mergeModel = model
|
||||||
mergeProvider = chatViewModel.inferProviderPublic(from: model.id) ?? mergeProvider
|
|
||||||
showModelPicker = false
|
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() {
|
private func combine() {
|
||||||
isProcessing = true
|
isProcessing = true
|
||||||
errorMessage = nil
|
errorMessage = nil
|
||||||
|
|||||||
Reference in New Issue
Block a user