Download Sources/PromptGarageApp/Views/ImportCenterView.swift from smlflg/PromtGarageMac: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageApp/Views/ImportCenterView.swift
- Command line
-
hf download hf://smlflg/PromtGarageMac/Sources/PromptGarageApp/Views/ImportCenterView.swift
-
curl -L -o ImportCenterView.swift https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageApp/Views/ImportCenterView.swift
10.9 kB
| import PromptGarageCore | |
| import SwiftUI | |
| struct ImportCenterView: View { | |
| var model: AppModel | |
| var body: some View { | |
| ScrollView { | |
| VStack(alignment: .leading, spacing: 22) { | |
| VStack(alignment: .leading, spacing: 7) { | |
| Text("Import Center") | |
| .font(.largeTitle.weight(.semibold)) | |
| Text("Quelle lokal analysieren, Ergebnis prüfen und erst danach atomar importieren.") | |
| .foregroundStyle(.secondary) | |
| } | |
| providerPicker | |
| formatAndSourceCard | |
| if let preview = model.importPreview { | |
| previewCard(preview) | |
| } else if model.importSourceURL == nil { | |
| ContentUnavailableView { | |
| Label("Noch keine Quelle", systemImage: "square.and.arrow.down") | |
| } description: { | |
| Text("Wähle Provider, Format und anschließend eine Datei oder einen Ordner.") | |
| } | |
| .frame(maxWidth: .infinity, minHeight: 210) | |
| } | |
| if let report = model.lastImportReport { | |
| ImportReportCard(report: report) | |
| } | |
| } | |
| .padding(24) | |
| .frame(maxWidth: 760) | |
| .frame(maxWidth: .infinity) | |
| } | |
| .navigationTitle("Import") | |
| .overlay { | |
| if model.isAnalyzingImport { | |
| LoadingOverlay(label: "Quelle wird lokal analysiert …") | |
| } else if model.isCommittingImport { | |
| LoadingOverlay(label: "Import wird atomar geschrieben …") | |
| } | |
| } | |
| } | |
| private var providerPicker: some View { | |
| Picker( | |
| "Provider", | |
| selection: Binding( | |
| get: { model.selectedImportProvider }, | |
| set: { model.selectImportProvider($0) } | |
| ) | |
| ) { | |
| ForEach(ImportProvider.allCases) { provider in | |
| Text(provider.displayName).tag(provider) | |
| } | |
| } | |
| .pickerStyle(.segmented) | |
| .disabled(model.isAnalyzingImport || model.isCommittingImport) | |
| } | |
| private var formatAndSourceCard: some View { | |
| VStack(alignment: .leading, spacing: 16) { | |
| HStack(alignment: .firstTextBaseline) { | |
| Text("Format") | |
| .font(.headline) | |
| Spacer() | |
| StatusChip( | |
| title: model.selectedImportProvider.displayName, | |
| systemImage: "shippingbox", | |
| tint: AppUI.accent | |
| ) | |
| } | |
| Picker( | |
| "Importformat", | |
| selection: Binding( | |
| get: { model.selectedImportFormat }, | |
| set: { model.selectImportFormat($0) } | |
| ) | |
| ) { | |
| ForEach(model.selectedImportProvider.formats) { format in | |
| Text(format.displayName).tag(format) | |
| } | |
| } | |
| .labelsHidden() | |
| .disabled(model.isAnalyzingImport || model.isCommittingImport) | |
| HStack(spacing: 12) { | |
| Image(systemName: model.importSourceURL == nil ? "doc.badge.plus" : "doc.fill") | |
| .font(.title2) | |
| .foregroundStyle(model.importSourceURL == nil ? .secondary : AppUI.accent) | |
| VStack(alignment: .leading, spacing: 2) { | |
| Text(model.importSourceURL?.lastPathComponent ?? "Datei oder Ordner auswählen") | |
| .font(.callout.weight(.medium)) | |
| .lineLimit(1) | |
| Text(model.importSourceURL?.path(percentEncoded: false) ?? "Es werden nur bewusst ausgewählte lokale Daten gelesen.") | |
| .font(.caption) | |
| .foregroundStyle(.secondary) | |
| .lineLimit(1) | |
| .truncationMode(.middle) | |
| } | |
| Spacer() | |
| Button("Auswählen & analysieren …") { | |
| Task { await model.chooseAndPreviewImportSource() } | |
| } | |
| .buttonStyle(.borderedProminent) | |
| .disabled(model.isAnalyzingImport || model.isCommittingImport) | |
| } | |
| .padding(14) | |
| .background(Color(nsColor: .controlBackgroundColor), in: RoundedRectangle(cornerRadius: 10)) | |
| } | |
| .padding(18) | |
| .background(.regularMaterial, in: RoundedRectangle(cornerRadius: AppUI.cardCornerRadius)) | |
| .overlay { | |
| RoundedRectangle(cornerRadius: AppUI.cardCornerRadius) | |
| .stroke(Color(nsColor: .separatorColor).opacity(0.6)) | |
| } | |
| } | |
| private func previewCard(_ preview: ImportPreview) -> some View { | |
| VStack(alignment: .leading, spacing: 16) { | |
| HStack { | |
| Label("Vorschau", systemImage: "doc.text.magnifyingglass") | |
| .font(.headline) | |
| Spacer() | |
| StatusChip( | |
| title: "Keine Änderung vorgenommen", | |
| systemImage: "eye", | |
| tint: .secondary | |
| ) | |
| } | |
| LazyVGrid(columns: [GridItem(.adaptive(minimum: 140), spacing: 10)], spacing: 10) { | |
| ImportMetric(title: "Gescannt", value: preview.statistics.scanned) | |
| ImportMetric(title: "Neu", value: preview.candidates.count, tint: .green) | |
| ImportMetric(title: "Duplikate", value: preview.statistics.skippedDuplicate) | |
| ImportMetric(title: "Agent", value: preview.statistics.skippedAgent) | |
| ImportMetric(title: "System", value: preview.statistics.skippedSystem) | |
| ImportMetric(title: "Zu kurz", value: preview.statistics.skippedShort) | |
| ImportMetric(title: "Zu lang", value: preview.statistics.skippedLong) | |
| ImportMetric(title: "Fehlerhaft", value: preview.statistics.skippedMalformed) | |
| } | |
| HStack { | |
| Button("Vorschau verwerfen") { | |
| model.resetImportPreview() | |
| } | |
| .disabled(model.isCommittingImport) | |
| Spacer() | |
| Button { | |
| Task { await model.commitImport() } | |
| } label: { | |
| Label( | |
| "\(preview.candidates.count.formatted()) Prompts importieren", | |
| systemImage: "square.and.arrow.down.fill" | |
| ) | |
| } | |
| .buttonStyle(.borderedProminent) | |
| .disabled(preview.candidates.isEmpty || !model.canMutatePrompts) | |
| } | |
| Label( | |
| "Ein Schreibfehler rollt den gesamten Import zurück. Es gibt keinen Teilimport.", | |
| systemImage: "arrow.counterclockwise.circle" | |
| ) | |
| .font(.caption) | |
| .foregroundStyle(.secondary) | |
| } | |
| .padding(18) | |
| .background(AppUI.accent.opacity(0.055), in: RoundedRectangle(cornerRadius: AppUI.cardCornerRadius)) | |
| .overlay { | |
| RoundedRectangle(cornerRadius: AppUI.cardCornerRadius) | |
| .stroke(AppUI.accent.opacity(0.22)) | |
| } | |
| } | |
| } | |
| private struct ImportMetric: View { | |
| let title: String | |
| let value: Int | |
| var tint: Color = .secondary | |
| var body: some View { | |
| VStack(alignment: .leading, spacing: 3) { | |
| Text(value.formatted()) | |
| .font(.title3.weight(.semibold).monospacedDigit()) | |
| .foregroundStyle(tint) | |
| Text(title) | |
| .font(.caption) | |
| .foregroundStyle(.secondary) | |
| } | |
| .frame(maxWidth: .infinity, alignment: .leading) | |
| .padding(11) | |
| .background(Color(nsColor: .controlBackgroundColor), in: RoundedRectangle(cornerRadius: 9)) | |
| } | |
| } | |
| private struct ImportReportCard: View { | |
| let report: ImportReport | |
| var body: some View { | |
| HStack(spacing: 12) { | |
| Image(systemName: "checkmark.circle.fill") | |
| .font(.title2) | |
| .foregroundStyle(.green) | |
| VStack(alignment: .leading, spacing: 3) { | |
| Text("Import abgeschlossen") | |
| .font(.headline) | |
| Text("\(report.statistics.imported.formatted()) Prompts aus \(report.sourceName)") | |
| .font(.callout) | |
| .foregroundStyle(.secondary) | |
| } | |
| Spacer() | |
| Text(report.completedAt, style: .time) | |
| .font(.caption.monospacedDigit()) | |
| .foregroundStyle(.tertiary) | |
| } | |
| .padding(16) | |
| .background(.green.opacity(0.07), in: RoundedRectangle(cornerRadius: AppUI.cardCornerRadius)) | |
| } | |
| } | |
| struct ImportPreviewDetailView: View { | |
| var model: AppModel | |
| var body: some View { | |
| Group { | |
| if let preview = model.importPreview, !preview.candidates.isEmpty { | |
| VStack(spacing: 0) { | |
| HStack { | |
| VStack(alignment: .leading, spacing: 2) { | |
| Text("Neue Prompts") | |
| .font(.title2.weight(.semibold)) | |
| Text("Nur Vorschau · noch nicht gespeichert") | |
| .font(.caption) | |
| .foregroundStyle(.secondary) | |
| } | |
| Spacer() | |
| Text(preview.candidates.count.formatted()) | |
| .font(.title2.weight(.semibold).monospacedDigit()) | |
| } | |
| .padding(18) | |
| Divider() | |
| List(preview.candidates.prefix(200), id: \.self) { candidate in | |
| VStack(alignment: .leading, spacing: 5) { | |
| Text(candidate.title) | |
| .font(.body.weight(.medium)) | |
| .lineLimit(1) | |
| Text(candidate.content.replacingOccurrences(of: "\n", with: " ")) | |
| .font(.caption) | |
| .foregroundStyle(.secondary) | |
| .lineLimit(2) | |
| StatusChip(title: candidate.source, systemImage: "shippingbox") | |
| } | |
| .padding(.vertical, 4) | |
| } | |
| } | |
| } else { | |
| ContentUnavailableView { | |
| Label("Importvorschau", systemImage: "doc.text.magnifyingglass") | |
| } description: { | |
| Text("Nach der lokalen Analyse erscheinen hier die erkannten neuen Prompts.") | |
| } | |
| } | |
| } | |
| .navigationTitle("Vorschau") | |
| } | |
| } | |