2.5.0 #10

Merged
rune merged 41 commits from 2.5.0 into main 2026-08-03 08:33:23 +02:00
Showing only changes of commit 37734232f5 - Show all commits
@@ -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,11 +51,8 @@ struct CombineConversationsSheet: View {
}
private var mergeModelLabel: String? {
if let mergeModel {
return "\(mergeProvider.displayName) / \(mergeModel.name)"
}
guard let model = settings.defaultModel, !model.isEmpty else { return nil }
return "\(settings.defaultProvider.displayName) / \(model)"
guard let mergeModel else { return nil }
return "\(mergeProvider.displayName) / \(mergeModel.name)"
}
private var isValid: Bool {
@@ -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