Download Sources/PromptGarageApp/Views/StatisticsView.swift from smlflg/PromtGarageMac: direct link, hf CLI and curl.
- Browser
- Download file 6.73 kB
-
https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageApp/Views/StatisticsView.swift
- Command line
-
hf download hf://smlflg/PromtGarageMac/Sources/PromptGarageApp/Views/StatisticsView.swift
-
curl -L -o StatisticsView.swift https://huggingface.co/smlflg/PromtGarageMac/resolve/main/Sources/PromptGarageApp/Views/StatisticsView.swift
6.73 kB
| import PromptGarageCore | |
| import SwiftUI | |
| struct StatisticsView: View { | |
| var model: AppModel | |
| var body: some View { | |
| ScrollView { | |
| VStack(alignment: .leading, spacing: 22) { | |
| VStack(alignment: .leading, spacing: 7) { | |
| Text("Statistik") | |
| .font(.largeTitle.weight(.semibold)) | |
| Text("Ein lokaler Blick auf deine Sammlung — ohne Sync- oder Cloud-Annahmen.") | |
| .foregroundStyle(.secondary) | |
| } | |
| LazyVGrid(columns: [GridItem(.adaptive(minimum: 175), spacing: 12)], spacing: 12) { | |
| MetricCard(title: "Gesamt", value: model.statistics.total, systemImage: "tray.full") | |
| MetricCard(title: "Favoriten", value: model.statistics.starred, systemImage: "star.fill") | |
| MetricCard(title: "Verwendungen", value: model.statistics.totalUses, systemImage: "arrow.up.forward.circle") | |
| } | |
| if model.statistics.total == 0 && !model.isLoadingStatistics { | |
| ContentUnavailableView { | |
| Label("Noch keine Statistik", systemImage: "chart.bar.xaxis") | |
| } description: { | |
| Text("Sobald Prompts vorhanden sind, erscheinen hier Quellen und Bewertungen.") | |
| } | |
| .frame(maxWidth: .infinity, minHeight: 260) | |
| } else { | |
| VStack(alignment: .leading, spacing: 13) { | |
| Text("Quellenfamilien") | |
| .font(.headline) | |
| ForEach(model.statistics.sourceFamilies.prefix(10)) { source in | |
| CountBar( | |
| name: source.name.capitalized, | |
| count: source.count, | |
| maximum: model.statistics.sourceFamilies.first?.count ?? 1, | |
| tint: AppUI.accent | |
| ) | |
| } | |
| } | |
| .statisticSection() | |
| VStack(alignment: .leading, spacing: 13) { | |
| Text("Bewertungen") | |
| .font(.headline) | |
| ForEach(Array((0...5).reversed()), id: \.self) { rating in | |
| CountBar( | |
| name: rating == 0 ? "Unbewertet" : String(repeating: "★", count: rating), | |
| count: model.statistics.ratingDistribution[rating, default: 0], | |
| maximum: model.statistics.ratingDistribution.values.max() ?? 1, | |
| tint: rating == 0 ? .secondary : AppUI.accent | |
| ) | |
| } | |
| } | |
| .statisticSection() | |
| } | |
| } | |
| .padding(24) | |
| .frame(maxWidth: 760) | |
| .frame(maxWidth: .infinity) | |
| } | |
| .navigationTitle("Statistik") | |
| .overlay { | |
| if model.isLoadingStatistics { | |
| LoadingOverlay(label: "Statistik berechnen …") | |
| } | |
| } | |
| .task { | |
| await model.refreshStatistics() | |
| } | |
| } | |
| } | |
| struct StatisticsDetailView: View { | |
| var model: AppModel | |
| var body: some View { | |
| ScrollView { | |
| VStack(alignment: .leading, spacing: 24) { | |
| VStack(alignment: .leading, spacing: 6) { | |
| Text("Top Tags") | |
| .font(.title2.weight(.semibold)) | |
| Text("Bestehende Tags, rein lokal gezählt.") | |
| .font(.callout) | |
| .foregroundStyle(.secondary) | |
| } | |
| if model.statistics.tags.isEmpty { | |
| ContentUnavailableView { | |
| Label("Keine Tags", systemImage: "tag") | |
| } description: { | |
| Text("Tagge Prompts, um Themen in deiner Sammlung sichtbar zu machen.") | |
| } | |
| .frame(minHeight: 240) | |
| } else { | |
| ForEach(model.statistics.tags.prefix(20)) { tag in | |
| CountBar( | |
| name: tag.name, | |
| count: tag.count, | |
| maximum: model.statistics.tags.first?.count ?? 1, | |
| tint: .blue | |
| ) | |
| } | |
| } | |
| Divider() | |
| VStack(alignment: .leading, spacing: 12) { | |
| Text("Modelle") | |
| .font(.headline) | |
| ForEach(model.statistics.models.prefix(10)) { modelName in | |
| HStack { | |
| Text(modelName.name) | |
| .lineLimit(1) | |
| Spacer() | |
| Text(modelName.count.formatted()) | |
| .font(.callout.monospacedDigit()) | |
| .foregroundStyle(.secondary) | |
| } | |
| } | |
| } | |
| } | |
| .padding(24) | |
| .frame(maxWidth: 660, alignment: .leading) | |
| .frame(maxWidth: .infinity) | |
| } | |
| .navigationTitle("Sammlung") | |
| } | |
| } | |
| private struct CountBar: View { | |
| let name: String | |
| let count: Int | |
| let maximum: Int | |
| let tint: Color | |
| private var fraction: Double { | |
| guard maximum > 0 else { return 0 } | |
| return min(1, Double(count) / Double(maximum)) | |
| } | |
| var body: some View { | |
| VStack(spacing: 6) { | |
| HStack { | |
| Text(name) | |
| .lineLimit(1) | |
| Spacer() | |
| Text(count.formatted()) | |
| .font(.callout.monospacedDigit()) | |
| .foregroundStyle(.secondary) | |
| } | |
| GeometryReader { proxy in | |
| ZStack(alignment: .leading) { | |
| Capsule().fill(.quaternary) | |
| Capsule() | |
| .fill(tint) | |
| .frame(width: max(3, proxy.size.width * fraction)) | |
| } | |
| } | |
| .frame(height: 6) | |
| } | |
| } | |
| } | |
| private extension View { | |
| func statisticSection() -> some View { | |
| padding(18) | |
| .background(.regularMaterial, in: RoundedRectangle(cornerRadius: AppUI.cardCornerRadius)) | |
| .overlay { | |
| RoundedRectangle(cornerRadius: AppUI.cardCornerRadius) | |
| .stroke(Color(nsColor: .separatorColor).opacity(0.6)) | |
| } | |
| } | |
| } | |