// // DatasetsView.swift // Zyquo MLX // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // import SwiftUI import UniformTypeIdentifiers /// Datasets section: import, validate, preview (charter §4.2). struct DatasetsView: View { @Bindable var model: AppModel @State private var isImporting = false @State private var importError: String? var body: some View { Group { if model.datasets.isEmpty { EmptyStateView( icon: "tablecells", title: "No Datasets Yet", message: "Import a JSONL file — chat conversations, prompt/completion pairs, or raw text. Zyquo validates every row, reports fixable issues, and splits it for training.", actionLabel: "Import JSONL…", action: { isImporting = true }) } else { ScrollView { LazyVStack(spacing: ZyquoTheme.spacing8) { ForEach(model.datasets) { dataset in DatasetRow(dataset: dataset) } } .padding(ZyquoTheme.spacing20) } .toolbar { ToolbarItem { Button("Import JSONL…") { isImporting = true } } } } } .fileImporter( isPresented: $isImporting, allowedContentTypes: [UTType(filenameExtension: "jsonl") ?? .json] ) { result in if case .success(let url) = result { Task { do { let gotAccess = url.startAccessingSecurityScopedResource() defer { if gotAccess { url.stopAccessingSecurityScopedResource() } } _ = try await DatasetService.shared.importJSONL( from: url, name: url.deletingPathExtension().lastPathComponent) await model.refresh() } catch { importError = error.localizedDescription } } } } .alert("Import Failed", isPresented: .constant(importError != nil)) { Button("OK") { importError = nil } } message: { Text(importError ?? "") } } } struct DatasetRow: View { let dataset: Dataset var body: some View { HStack(spacing: ZyquoTheme.spacing12) { Image(systemName: "tablecells") .font(.system(size: 18, weight: .light)) .foregroundStyle(ZyquoTheme.slate) .frame(width: 32, height: 32) .background(ZyquoTheme.surfaceSecondary) .clipShape(RoundedRectangle(cornerRadius: ZyquoTheme.radiusSmall)) VStack(alignment: .leading, spacing: ZyquoTheme.spacing2) { Text(dataset.name) .font(ZyquoTheme.bodyFont.weight(.medium)) .foregroundStyle(ZyquoTheme.textPrimary) HStack(spacing: ZyquoTheme.spacing8) { Text(dataset.format.displayName) Text("train \(dataset.trainCount) · valid \(dataset.validCount)" + (dataset.testCount > 0 ? " · test \(dataset.testCount)" : "")) if let tokens = dataset.totalTokens { Text("≈\(tokens.formatted()) tokens") } } .font(ZyquoTheme.captionFont) .foregroundStyle(ZyquoTheme.textSecondary) } Spacer() Text(dataset.importedAt.formatted(date: .abbreviated, time: .omitted)) .font(ZyquoTheme.captionFont) .foregroundStyle(ZyquoTheme.textTertiary) } .padding(ZyquoTheme.spacing12) .zyquoCard() } }