// // HeaderView.swift // oAI // // Header bar with provider, model, and stats // import SwiftUI struct HeaderView: View { let provider: Settings.Provider let model: ModelInfo? let stats: SessionStats let onlineMode: Bool let mcpEnabled: Bool let mcpStatus: String? let onModelSelect: () -> Void let onProviderChange: (Settings.Provider) -> Void private let settings = SettingsService.shared private let registry = ProviderRegistry.shared var body: some View { HStack(spacing: 12) { // Provider picker dropdown — only shows configured providers Menu { ForEach(registry.configuredProviders, id: \.self) { p in Button { onProviderChange(p) } label: { HStack { Image(systemName: p.iconName) Text(p.displayName) if p == provider { Image(systemName: "checkmark") } } } } } label: { HStack(spacing: 4) { Image(systemName: provider.iconName) .font(.system(size: settings.guiTextSize - 2)) Text(provider.displayName) .font(.system(size: settings.guiTextSize - 2, weight: .medium)) Image(systemName: "chevron.up.chevron.down") .font(.system(size: 8)) .opacity(0.7) } .foregroundColor(.white) .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.providerColor(provider)) .cornerRadius(4) } .menuStyle(.borderlessButton) .fixedSize() .help("Switch provider") // Model info (clickable → model selector) Button(action: onModelSelect) { if let model = model { HStack(spacing: 6) { Text(model.name) .font(.system(size: settings.guiTextSize, weight: .medium)) .foregroundColor(.oaiPrimary) // Capability badges HStack(spacing: 3) { if model.capabilities.vision { Image(systemName: "eye") .font(.system(size: 9)) .foregroundColor(.oaiSecondary) } if model.capabilities.tools { Image(systemName: "wrench") .font(.system(size: 9)) .foregroundColor(.oaiSecondary) } if model.capabilities.online { Image(systemName: "globe") .font(.system(size: 9)) .foregroundColor(.oaiSecondary) } if model.capabilities.imageGeneration { Image(systemName: "paintbrush") .font(.system(size: 9)) .foregroundColor(.oaiSecondary) } } Image(systemName: "chevron.down") .font(.caption2) .foregroundColor(.oaiSecondary) } } else { HStack(spacing: 4) { Text("No model selected") .font(.system(size: settings.guiTextSize)) .foregroundColor(.oaiSecondary) Image(systemName: "chevron.down") .font(.caption2) .foregroundColor(.oaiSecondary) } } } .buttonStyle(.plain) .help("Select model") Spacer() // Status indicators HStack(spacing: 8) { if model?.capabilities.imageGeneration == true { StatusPill(icon: "paintbrush", label: "Image", color: .purple) } if onlineMode { StatusPill(icon: "globe", label: "Online", color: .green) } if mcpEnabled { StatusPill(icon: "folder", label: "MCP", color: .blue) } } // Divider between status and stats if onlineMode || mcpEnabled || model?.capabilities.imageGeneration == true { Divider() .frame(height: 16) .opacity(0.5) } // Quick stats HStack(spacing: 16) { StatItem(icon: "message", value: "\(stats.messageCount)") StatItem(icon: "arrow.up.arrow.down", value: stats.totalTokensDisplay) StatItem(icon: "dollarsign", value: stats.totalCostDisplay) } .font(.caption) } .padding(.horizontal, 16) .padding(.vertical, 10) .background(.ultraThinMaterial) .overlay( Rectangle() .fill(Color.oaiBorder.opacity(0.5)) .frame(height: 1), alignment: .bottom ) } } struct StatItem: View { let icon: String let value: String private let settings = SettingsService.shared var body: some View { HStack(spacing: 4) { Image(systemName: icon) .font(.system(size: settings.guiTextSize - 3)) .foregroundColor(.oaiSecondary) Text(value) .font(.system(size: settings.guiTextSize - 1, weight: .medium)) .foregroundColor(.oaiPrimary) } } } struct StatusPill: View { let icon: String let label: String let color: Color var body: some View { HStack(spacing: 3) { Circle() .fill(color) .frame(width: 6, height: 6) Text(label) .font(.system(size: 10, weight: .medium)) .foregroundColor(.oaiSecondary) } .padding(.horizontal, 6) .padding(.vertical, 2) .background(color.opacity(0.1), in: Capsule()) } } #Preview { VStack { HeaderView( provider: .openrouter, model: ModelInfo.mockModels.first, stats: SessionStats( totalInputTokens: 125, totalOutputTokens: 434, totalCost: 0.00111, messageCount: 4 ), onlineMode: true, mcpEnabled: true, mcpStatus: "MCP", onModelSelect: {}, onProviderChange: { _ in } ) Spacer() } .background(Color.oaiBackground) }