// // ModelsView.swift // Zyquo MLX // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import SwiftUI /// Models section: installed local library + Discover (Hub search wired in /// Phase 6). struct ModelsView: View { @Bindable var model: AppModel @State private var tab: Tab = .installed enum Tab: String, CaseIterable { case installed = "Installed" case discover = "Discover" } var body: some View { VStack(spacing: 0) { Picker("", selection: $tab) { ForEach(Tab.allCases, id: \.self) { Text($0.rawValue) } } .pickerStyle(.segmented) .frame(width: 240) .padding(.vertical, ZyquoTheme.spacing12) switch tab { case .installed: installed case .discover: discover } } } @ViewBuilder private var installed: some View { if model.models.isEmpty { EmptyStateView( icon: "shippingbox", title: "No Models Yet", message: "Your foundry starts with a model. Discover curated MLX models from the community, or drop an existing MLX model folder into your library.", actionLabel: "Discover Models", action: { tab = .discover }) } else { ScrollView { LazyVStack(spacing: ZyquoTheme.spacing8) { ForEach(model.models) { item in ModelRow(item: item) } } .padding(ZyquoTheme.spacing20) } } } private var discover: some View { DiscoverView(model: model) } } struct ModelRow: View { let item: LocalModel @State private var isHovering = false var body: some View { HStack(spacing: ZyquoTheme.spacing12) { Image(systemName: iconName) .font(.system(size: 18, weight: .light)) .foregroundStyle(ZyquoTheme.accent) .frame(width: 32, height: 32) .background(ZyquoTheme.accentSubtle) .clipShape(RoundedRectangle(cornerRadius: ZyquoTheme.radiusSmall)) VStack(alignment: .leading, spacing: ZyquoTheme.spacing2) { Text(item.repoID ?? item.name) .font(ZyquoTheme.bodyFont.weight(.medium)) .foregroundStyle(ZyquoTheme.textPrimary) .lineLimit(1) HStack(spacing: ZyquoTheme.spacing8) { Text(item.displayParams) if let quant = item.quantization { Text(quant.label) } Text(ByteCountFormatter.string(fromByteCount: item.diskSize, countStyle: .file)) } .font(ZyquoTheme.captionFont) .foregroundStyle(ZyquoTheme.textSecondary) } Spacer() TypeBadge(type: item.type) VerdictBadge(verdict: MemoryAdvisor.inferenceVerdict(for: item)) Button { NSWorkspace.shared.activateFileViewerSelecting([item.directory]) } label: { Image(systemName: "magnifyingglass") .font(.system(size: 11)) .foregroundStyle(ZyquoTheme.textTertiary) } .buttonStyle(.plain) .help("Reveal in Finder") .opacity(isHovering ? 1 : 0) } .padding(ZyquoTheme.spacing12) .zyquoCard() .onHover { isHovering = $0 } } private var iconName: String { switch item.type { case .llm: "text.alignleft" case .vlm: "photo.on.rectangle.angled" case .embedding: "point.3.connected.trianglepath.dotted" case .speech: "waveform" case .imageGeneration: "paintbrush" } } }