spb/prisme Public MIT
Navigateur iOS intelligent — chaque page comprise localement avant d'être affichée. SwiftUI · WebKit · Foundation Models, 100% on-device.
Swift 96.7%
JavaScript 3.3%
1//2// InsightStrip.swift3// Prisme4//5// Author: Simon-Pierre Boucher <contact@spboucher.ai>6//78import SwiftUI910/// The understanding strip: after a page settles, Prisme shows what it11/// read — instantly and deterministically (reading time, structure), then12/// enriched with the page kind and essence when the local model delivers.13/// This is the thesis made visible ("chaque page est comprise avant d'être14/// affichée") without the user hunting for a button. One tap opens the15/// reader; the × dismisses it for this page. Never a popup, never modal.16struct InsightStrip: View {17 let insight: PageInsight18 let digest: PageDigest?19 let accent: Color20 let onRead: () -> Void21 let onDismiss: () -> Void2223 /// Single tint for generated content, everywhere in the app (§7).24 private static let generated = Color(red: 0.55, green: 0.36, blue: 0.96)2526 var body: some View {27 HStack(spacing: Spacing.m) {28 Button(action: onRead) {29 HStack(spacing: Spacing.m) {30 Image(systemName: "doc.plaintext")31 .font(.subheadline.weight(.semibold))32 .foregroundStyle(accent)3334 VStack(alignment: .leading, spacing: 2) {35 Text(headline)36 .font(.footnote.weight(.semibold))37 .foregroundStyle(.primary)38 if let gist = digest?.gist, !gist.isEmpty {39 HStack(alignment: .top, spacing: Spacing.xs) {40 Image(systemName: "sparkles")41 .font(.caption2)42 .padding(.top, 2)43 Text(gist)44 .font(.caption)45 .italic()46 .lineLimit(2)47 }48 .foregroundStyle(Self.generated)49 }50 }5152 Spacer(minLength: Spacing.s)5354 Text("Lire")55 .font(.footnote.weight(.semibold))56 .foregroundStyle(.white)57 .padding(.horizontal, Spacing.m)58 .padding(.vertical, Spacing.xs + 2)59 .background(accent, in: Capsule())60 }61 .contentShape(Rectangle())62 }63 .buttonStyle(.plain)64 .accessibilityIdentifier("insight.read")6566 Button(action: onDismiss) {67 Image(systemName: "xmark")68 .font(.caption.bold())69 .foregroundStyle(.secondary)70 .frame(width: 28, height: 28)71 .contentShape(Circle())72 }73 .buttonStyle(.plain)74 .accessibilityIdentifier("insight.dismiss")75 }76 .padding(.horizontal, Spacing.l)77 .padding(.vertical, Spacing.m)78 .glassEffect(.regular, in: RoundedRectangle(cornerRadius: Radius.l))79 }8081 private var headline: String {82 var parts: [String] = []83 if let kind = digest?.kind {84 parts.append(kind.label)85 }86 parts.append("~\(insight.readingMinutes) min de lecture")87 if digest == nil, insight.sectionCount > 1 {88 parts.append("\(insight.sectionCount) sections")89 }90 return parts.joined(separator: " · ")91 }92}93