diff --git a/clients/macos/vellum-assistant/Features/Settings/ProvidersSheet.swift b/clients/macos/vellum-assistant/Features/Settings/ProvidersSheet.swift index 72286929365..ade1d15e545 100644 --- a/clients/macos/vellum-assistant/Features/Settings/ProvidersSheet.swift +++ b/clients/macos/vellum-assistant/Features/Settings/ProvidersSheet.swift @@ -374,7 +374,16 @@ struct ProvidersSheet: View { } .onChange(of: editorDraft.provider) { _, newProvider in if case .create = editorState, !newProvider.isEmpty { - editorDraft.credential = "credential/\(newProvider)/api_key" + if newProvider == "ollama" { + editorDraft.authType = "none" + editorDraft.credential = "" + } else { + if editorDraft.authType == "none" || + (editorDraft.authType == "platform" && !store.isManagedCapable(newProvider)) { + editorDraft.authType = "api_key" + } + editorDraft.credential = "credential/\(newProvider)/api_key" + } maskedCredentialValue = nil Task { await loadAvailableCredentials() } } @@ -403,7 +412,8 @@ struct ProvidersSheet: View { } Spacer(minLength: 0) VButton( - label: "Cancel", + label: "Close", + iconOnly: VIcon.x.rawValue, style: .ghost, tintColor: VColor.contentTertiary ) { @@ -481,6 +491,33 @@ struct ProvidersSheet: View { } } + private var authTypeOptions: [(label: String, value: String)] { + let provider = editorDraft.provider + if provider == "ollama" { + return [(label: "None (no credentials)", value: "none")] + } + var options: [(label: String, value: String)] = [ + (label: "API Key", value: "api_key"), + ] + if store.isManagedCapable(provider) { + options.append((label: "Platform (managed by Vellum)", value: "platform")) + } + // Preserve the current auth type in edit mode so existing connections + // display their saved value even if the type is no longer offered for + // new connections (e.g. a non-ollama connection with "none" auth). + let current = editorDraft.authType + if !current.isEmpty && !options.contains(where: { $0.value == current }) { + let label: String = switch current { + case "none": "None (no credentials)" + case "platform": "Platform (managed by Vellum)" + case "api_key": "API Key" + default: current + } + options.append((label: label, value: current)) + } + return options + } + private var editorAuthTypeField: some View { VStack(alignment: .leading, spacing: VSpacing.xs) { Text("Auth Type") @@ -489,11 +526,7 @@ struct ProvidersSheet: View { VDropdown( placeholder: "Select auth type\u{2026}", selection: $editorDraft.authType, - options: [ - (label: "API Key", value: "api_key"), - (label: "Platform (managed by Vellum)", value: "platform"), - (label: "None (no credentials)", value: "none"), - ] + options: authTypeOptions ) } } @@ -764,7 +797,11 @@ struct ProvidersSheet: View { provider: provider ) if !provider.isEmpty { - editorDraft.credential = "credential/\(provider)/api_key" + if provider == "ollama" { + editorDraft.authType = "none" + } else { + editorDraft.credential = "credential/\(provider)/api_key" + } } editorState = .create maskedCredentialValue = nil @@ -849,13 +886,13 @@ struct ProvidersSheet: View { existingNames: Set(connections.map { $0.name }) ) isKeyDirty = true - // Default to api_key auth — the whole reason to clone a managed - // connection is to use your own key, so `platform` isn't the - // right default for the fork. Seed the credential reference to - // the provider's default slot so an immediate save (with no - // Advanced expansion) lands the key under `credential//api_key`. - editorDraft.authType = "api_key" - editorDraft.credential = "credential/\(provider)/api_key" + if provider == "ollama" { + editorDraft.authType = "none" + editorDraft.credential = "" + } else { + editorDraft.authType = "api_key" + editorDraft.credential = "credential/\(provider)/api_key" + } editorDraft.apiKeyValue = "" // New connection starts active by convention; user can toggle off // before saving if they want it disabled. diff --git a/clients/shared/DesignSystem/Core/Inputs/VDropdown.swift b/clients/shared/DesignSystem/Core/Inputs/VDropdown.swift index b1bf9f17c30..5843814ed80 100644 --- a/clients/shared/DesignSystem/Core/Inputs/VDropdown.swift +++ b/clients/shared/DesignSystem/Core/Inputs/VDropdown.swift @@ -44,7 +44,7 @@ public struct VDropdown: View { @Binding public var selection: T public var emptyValue: T? public var maxWidth: CGFloat = .infinity - public var menuWidth: CGFloat = 180 + public var menuWidth: CGFloat? public var menuMaxHeight: CGFloat? public var icon: VIcon? public var optionIcon: ((T) -> VIcon?)? @@ -60,7 +60,7 @@ public struct VDropdown: View { selection: Binding, emptyValue: T? = nil, maxWidth: CGFloat = .infinity, - menuWidth: CGFloat = 180, + menuWidth: CGFloat? = nil, menuMaxHeight: CGFloat? = nil, icon: VIcon? = nil, optionIcon: ((T) -> VIcon?)? = nil, @@ -87,7 +87,7 @@ public struct VDropdown: View { options: [(label: String, value: T)], emptyValue: T? = nil, maxWidth: CGFloat = .infinity, - menuWidth: CGFloat = 180, + menuWidth: CGFloat? = nil, menuMaxHeight: CGFloat? = nil, icon: VIcon? = nil, optionIcon: ((T) -> VIcon?)? = nil, @@ -196,8 +196,9 @@ public struct VDropdown: View { // window behind it (e.g. the Share Feedback modal over the main app // window) — attaching to the wrong parent shoves the modal behind via // `addChildWindow`. + let effectiveMenuWidth = menuWidth ?? triggerFrame.width activePanel = VMenuPanel.show(at: screenPoint, sourceWindow: window, sourceAppearance: appearance, excludeRect: triggerScreenRect) { - VMenu(width: menuWidth, maxHeight: menuMaxHeight) { + VMenu(width: effectiveMenuWidth, maxHeight: menuMaxHeight) { ForEach(optionList) { option in VMenuItem( icon: option.icon?.rawValue ?? optionIcon?(option.value)?.rawValue,