// // ModelInfoView.swift // oAI // // Rich model information modal // import SwiftUI struct ModelInfoView: View { let model: ModelInfo @Environment(\.dismiss) var dismiss var body: some View { VStack(spacing: 0) { // Header HStack { Text("Model Info") .font(.system(size: 18, weight: .bold)) Spacer() Button { dismiss() } label: { Image(systemName: "xmark.circle.fill") .font(.title2) .foregroundStyle(.secondary) } .buttonStyle(.plain) .keyboardShortcut(.escape, modifiers: []) } .padding(.horizontal, 24) .padding(.top, 20) .padding(.bottom, 12) Divider() ScrollView { VStack(alignment: .leading, spacing: 20) { // Overview sectionHeader("Overview") infoRow("Name", model.name) infoRow("ID", model.id) if let provider = model.topProvider { infoRow("Provider", provider) } if let desc = model.description { VStack(alignment: .leading, spacing: 6) { Text("Description") .font(.subheadline.weight(.medium)) .foregroundColor(.secondary) Text(desc) .font(.body) .foregroundColor(.primary) .fixedSize(horizontal: false, vertical: true) .textSelection(.enabled) } .padding(.leading, 4) } Divider() // Pricing sectionHeader("Pricing") infoRow("Input", model.promptPriceDisplay + " / 1M tokens") infoRow("Output", model.completionPriceDisplay + " / 1M tokens") if model.pricing.prompt > 0 { VStack(alignment: .leading, spacing: 6) { Text("Cost Examples") .font(.caption) .foregroundColor(.secondary) HStack(spacing: 16) { costExample(label: "1K tokens", inputTokens: 1_000) costExample(label: "10K tokens", inputTokens: 10_000) costExample(label: "100K tokens", inputTokens: 100_000) } } .padding(.leading, 4) } Divider() // Context Window sectionHeader("Context Window") infoRow("Max Tokens", model.contextLength.formatted()) if model.contextLength > 0 { VStack(alignment: .leading, spacing: 4) { let maxContext = 2_000_000.0 let fraction = min(Double(model.contextLength) / maxContext, 1.0) ZStack(alignment: .leading) { RoundedRectangle(cornerRadius: 4) .fill(Color.gray.opacity(0.2)) .frame(height: 16) GeometryReader { geo in RoundedRectangle(cornerRadius: 4) .fill(Color.blue) .frame(width: geo.size.width * fraction, height: 16) } .frame(height: 16) } Text(model.contextLengthDisplay + " tokens") .font(.caption2) .foregroundColor(.secondary) } .padding(.leading, 4) } Divider() // Capabilities sectionHeader("Capabilities") HStack(spacing: 12) { capabilityBadge(icon: "eye.fill", label: "Vision", active: model.capabilities.vision) capabilityBadge(icon: "wrench.fill", label: "Tools", active: model.capabilities.tools) capabilityBadge(icon: "globe", label: "Online", active: model.capabilities.online) capabilityBadge(icon: "photo.fill", label: "Image Gen", active: model.capabilities.imageGeneration) } // Architecture (if available) if let arch = model.architecture { Divider() sectionHeader("Architecture") if let modality = arch.modality { infoRow("Modality", modality) } if let tokenizer = arch.tokenizer { infoRow("Tokenizer", tokenizer) } if let instructType = arch.instructType { infoRow("Instruct Type", instructType) } } } .padding(.horizontal, 24) .padding(.vertical, 16) } Divider() // Bottom bar HStack { Spacer() Button("Done") { dismiss() } .keyboardShortcut(.return, modifiers: []) .buttonStyle(.borderedProminent) .controlSize(.regular) Spacer() } .padding(.horizontal, 24) .padding(.vertical, 12) } .frame(minWidth: 550, idealWidth: 650, minHeight: 550, idealHeight: 750) } // MARK: - Layout Helpers private func sectionHeader(_ title: String) -> some View { Text(title) .font(.system(size: 13, weight: .semibold)) .foregroundStyle(.secondary) .textCase(.uppercase) } private func infoRow(_ label: String, _ value: String) -> some View { HStack { Text(label) .font(.body) Spacer() Text(value) .font(.body) .foregroundColor(.secondary) .textSelection(.enabled) } .padding(.leading, 4) } @ViewBuilder private func costExample(label: String, inputTokens: Int) -> some View { let cost = (Double(inputTokens) * model.pricing.prompt / 1_000_000) + (Double(inputTokens) * model.pricing.completion / 1_000_000) VStack(spacing: 2) { Text(label) .font(.caption2) .foregroundColor(.secondary) Text(String(format: "$%.4f", cost)) .font(.caption.monospacedDigit()) .foregroundColor(.primary) } .padding(.horizontal, 8) .padding(.vertical, 4) .background(Color.gray.opacity(0.1)) .cornerRadius(4) } @ViewBuilder private func capabilityBadge(icon: String, label: String, active: Bool) -> some View { VStack(spacing: 4) { Image(systemName: icon) .font(.title3) .foregroundColor(active ? .blue : .gray.opacity(0.4)) Text(label) .font(.caption2) .foregroundColor(active ? .primary : .secondary) } .frame(maxWidth: .infinity) .padding(.vertical, 8) .background(active ? Color.blue.opacity(0.1) : Color.gray.opacity(0.05)) .cornerRadius(8) } } #Preview { ModelInfoView(model: ModelInfo( id: "anthropic/claude-sonnet-4", name: "Claude Sonnet 4", description: "Balanced intelligence and speed. This is a longer description to test how the modal handles multi-line text that wraps across several lines in the description field.", contextLength: 200_000, pricing: .init(prompt: 3.0, completion: 15.0), capabilities: .init(vision: true, tools: true, online: false), architecture: .init(tokenizer: "claude", instructType: "claude", modality: "text+image->text"), topProvider: "anthropic" )) }