import SwiftUI struct ProviderSettingsSection: View { let title: String let spacing: CGFloat let verticalPadding: CGFloat let horizontalPadding: CGFloat @ViewBuilder let content: () -> Content init( title: String, spacing: CGFloat = 12, verticalPadding: CGFloat = 10, horizontalPadding: CGFloat = 4, @ViewBuilder content: @escaping () -> Content) { self.title = title self.spacing = spacing self.verticalPadding = verticalPadding self.horizontalPadding = horizontalPadding self.content = content } var body: some View { VStack(alignment: .leading, spacing: self.spacing) { Text(L(self.title)) .font(.headline) self.content() } .frame(maxWidth: .infinity, alignment: .leading) .padding(.vertical, self.verticalPadding) .padding(.horizontal, self.horizontalPadding) } } @MainActor struct ProviderSettingsToggleRowView: View { let toggle: ProviderSettingsToggleDescriptor var body: some View { let isEnabled = self.toggle.isEnabled?() ?? true VStack(alignment: .leading, spacing: 8) { HStack(alignment: .firstTextBaseline, spacing: 12) { VStack(alignment: .leading, spacing: 4) { Text(L(self.toggle.title)) .font(.subheadline.weight(.semibold)) .foregroundStyle(isEnabled ? .primary : .tertiary) Text(L(self.toggle.subtitle)) .font(.footnote) .foregroundStyle(isEnabled ? .secondary : .tertiary) .fixedSize(horizontal: false, vertical: true) } Spacer(minLength: 8) Toggle("", isOn: self.toggle.binding) .labelsHidden() .toggleStyle(.switch) } if self.toggle.binding.wrappedValue { if let status = self.toggle.statusText?(), !status.isEmpty { Text(status) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(4) .fixedSize(horizontal: false, vertical: true) } let actions = self.toggle.actions.filter { $0.isVisible?() ?? true } if !actions.isEmpty { HStack(spacing: 10) { ForEach(actions) { action in Button(L(action.title)) { Task { @MainActor in await action.perform() } } .applyProviderSettingsButtonStyle(action.style) .controlSize(.small) } } } } } .disabled(!isEnabled) .onChange(of: self.toggle.binding.wrappedValue) { _, enabled in guard let onChange = self.toggle.onChange else { return } Task { @MainActor in await onChange(enabled) } } .task(id: self.toggle.binding.wrappedValue) { guard self.toggle.binding.wrappedValue else { return } guard let onAppear = self.toggle.onAppearWhenEnabled else { return } await onAppear() } } } @MainActor struct ProviderSettingsPickerRowView: View { let picker: ProviderSettingsPickerDescriptor var body: some View { let isEnabled = self.picker.isEnabled?() ?? true VStack(alignment: .leading, spacing: 6) { HStack(alignment: .firstTextBaseline, spacing: 10) { Text(L(self.picker.title)) .font(.subheadline.weight(.semibold)) .frame(width: ProviderSettingsMetrics.pickerLabelWidth, alignment: .leading) Picker("", selection: self.picker.binding) { ForEach(self.picker.options) { option in Text(L(option.title)).tag(option.id) } } .labelsHidden() .pickerStyle(.menu) .controlSize(.small) if let trailingText = self.picker.trailingText?(), !trailingText.isEmpty { Text(trailingText) .font(.footnote) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.tail) .padding(.leading, 4) } Spacer(minLength: 0) } let subtitle = self.picker.dynamicSubtitle?() ?? self.picker.subtitle if !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } .disabled(!isEnabled) .onChange(of: self.picker.binding.wrappedValue) { _, selection in guard let onChange = self.picker.onChange else { return } Task { @MainActor in await onChange(selection) } } } } @MainActor struct ProviderSettingsFieldRowView: View { let field: ProviderSettingsFieldDescriptor var body: some View { VStack(alignment: .leading, spacing: 8) { let trimmedTitle = self.field.title.trimmingCharacters(in: .whitespacesAndNewlines) let trimmedSubtitle = self.field.subtitle.trimmingCharacters(in: .whitespacesAndNewlines) let hasHeader = !trimmedTitle.isEmpty || !trimmedSubtitle.isEmpty if hasHeader { VStack(alignment: .leading, spacing: 4) { if !trimmedTitle.isEmpty { Text(L(trimmedTitle)) .font(.subheadline.weight(.semibold)) } if !trimmedSubtitle.isEmpty { Text(L(trimmedSubtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } } switch self.field.kind { case .plain: TextField(L(self.field.placeholder ?? ""), text: self.field.binding) .textFieldStyle(.roundedBorder) .font(.footnote) .onTapGesture { self.field.onActivate?() } case .secure: SecureField(L(self.field.placeholder ?? ""), text: self.field.binding) .textFieldStyle(.roundedBorder) .font(.footnote) .onTapGesture { self.field.onActivate?() } } let actions = self.field.actions.filter { $0.isVisible?() ?? true } if !actions.isEmpty { HStack(spacing: 10) { ForEach(actions) { action in Button(L(action.title)) { Task { @MainActor in await action.perform() } } .applyProviderSettingsButtonStyle(action.style) .controlSize(.small) } } } if let footer = self.field.footerText, !footer.isEmpty { Text(L(footer)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } } } } @MainActor struct ProviderSettingsActionsRowView: View { let descriptor: ProviderSettingsActionsDescriptor var body: some View { VStack(alignment: .leading, spacing: 8) { Text(L(self.descriptor.title)) .font(.subheadline.weight(.semibold)) if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(self.descriptor.subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } let actions = self.descriptor.actions.filter { $0.isVisible?() ?? true } if !actions.isEmpty { HStack(spacing: 10) { ForEach(actions) { action in Button(L(action.title)) { Task { @MainActor in await action.perform() } } .applyProviderSettingsButtonStyle(action.style) .controlSize(.small) } } } } } } @MainActor struct ProviderSettingsTokenAccountsRowView: View { let descriptor: ProviderSettingsTokenAccountsDescriptor @State private var newLabel: String = "" @State private var newToken: String = "" @State private var newOrgID: String = "" var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .center, spacing: 12) { Text(L(self.descriptor.title)) .font(.subheadline.weight(.semibold)) Spacer(minLength: 8) if let title = self.descriptor.primaryAddActionTitle, let action = self.descriptor.primaryAddAction { Button(L(title)) { Task { @MainActor in await action() } } .buttonStyle(.bordered) .controlSize(.small) } } if !self.descriptor.subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(self.descriptor.subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } let accounts = self.descriptor.accounts() if accounts.isEmpty { Text(L("No token accounts yet.")) .font(.footnote) .foregroundStyle(.secondary) } else { VStack(alignment: .leading, spacing: 8) { ForEach(Array(accounts.enumerated()), id: \.element.id) { index, account in HStack(alignment: .center, spacing: 10) { Button { self.descriptor.setActiveIndex(index) } label: { HStack(alignment: .center, spacing: 8) { Image(systemName: self.isActive(index: index, accountCount: accounts.count) ? "checkmark.circle.fill" : "circle") .font(.system(size: 13, weight: .medium)) .foregroundStyle(self.isActive(index: index, accountCount: accounts.count) ? Color.accentColor : Color.secondary) Text(account.displayName) .font( .footnote.weight( self.isActive(index: index, accountCount: accounts.count) ? .semibold : .regular)) .foregroundStyle(.primary) Spacer(minLength: 0) } .contentShape(Rectangle()) } .buttonStyle(.plain) Button(L("Remove")) { self.descriptor.removeAccount(account.id) } .buttonStyle(.bordered) .controlSize(.small) } if index < accounts.count - 1 { Divider() } } } } if self.descriptor.primaryAddAction == nil { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 8) { TextField(L("Label"), text: self.$newLabel) .textFieldStyle(.roundedBorder) .font(.footnote) SecureField(L(self.descriptor.placeholder), text: self.$newToken) .textFieldStyle(.roundedBorder) .font(.footnote) Button(L("Add")) { let label = self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines) let token = self.newToken.trimmingCharacters(in: .whitespacesAndNewlines) guard !label.isEmpty, !token.isEmpty else { return } let orgID = self.descriptor.showsOrganizationField ? self.newOrgID.trimmingCharacters(in: .whitespacesAndNewlines) : "" self.descriptor.addAccount(label, token, orgID.isEmpty ? nil : orgID) self.newLabel = "" self.newToken = "" self.newOrgID = "" } .buttonStyle(.bordered) .controlSize(.small) .disabled(self.newLabel.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || self.newToken.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) } if self.descriptor.showsOrganizationField { TextField(L("Org ID (optional)"), text: self.$newOrgID) .textFieldStyle(.roundedBorder) .font(.footnote) .help( L("Optional organization ID for accounts linked to multiple Anthropic organizations.")) } } } HStack(spacing: 10) { Button(L("Open token file")) { self.descriptor.openConfigFile() } .buttonStyle(.link) .controlSize(.small) Button(L("Reload")) { self.descriptor.reloadFromDisk() } .buttonStyle(.link) .controlSize(.small) } } } private func isActive(index: Int, accountCount: Int) -> Bool { guard accountCount > 0 else { return false } let selectedIndex = min(self.descriptor.activeIndex(), max(0, accountCount - 1)) return selectedIndex == index } } extension View { @ViewBuilder fileprivate func applyProviderSettingsButtonStyle(_ style: ProviderSettingsActionDescriptor.Style) -> some View { switch style { case .bordered: self.buttonStyle(.bordered) case .link: self.buttonStyle(.link) } } } @MainActor struct ProviderSettingsOrganizationsRowView: View { let descriptor: ProviderSettingsOrganizationsDescriptor @State private var errorMessage: String? @State private var isRefreshing = false var body: some View { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .center, spacing: 12) { Text(L(self.descriptor.title)) .font(.subheadline.weight(.semibold)) Spacer(minLength: 8) } if let subtitle = self.descriptor.subtitle, !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(L(subtitle)) .font(.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } let entries = self.descriptor.entries() if entries.allSatisfy(\.isLocked) { Text(L("No organizations loaded. Click Refresh after setting your API key.")) .font(.footnote) .foregroundStyle(.secondary) } else { VStack(alignment: .leading, spacing: 6) { ForEach(entries) { entry in Toggle(isOn: Binding( get: { entry.isEnabled }, set: { newValue in self.descriptor.onToggle(entry.id, newValue) })) { VStack(alignment: .leading, spacing: 1) { Text(entry.localizesTitle ? L(entry.title) : entry.title) .font(.footnote) if let subtitle = entry.subtitle, !subtitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { Text(entry.localizesSubtitle ? L(subtitle) : subtitle) .font(.caption) .foregroundStyle(.secondary) } } } .toggleStyle(.checkbox) .disabled(entry.isLocked) } } } HStack(spacing: 10) { Button(L("Refresh organizations")) { Task { @MainActor in self.isRefreshing = true let result = await self.descriptor.onRefresh() self.isRefreshing = false self.errorMessage = result.success ? nil : result.errorMessage } } .buttonStyle(.bordered) .controlSize(.small) .disabled(!self.descriptor.canRefresh() || self.isRefreshing) if let errorMessage = self.errorMessage, !errorMessage.isEmpty { Text(errorMessage) .font(.caption) .foregroundStyle(.red) } } } } }