// // PageDigest.swift // Prisme // // Author: Simon-Pierre Boucher // import Foundation import FoundationModels /// Structured output of the Distiller — never free-form text to parse /// (CLAUDE.md §9). Every generated element carries the index of the source /// content block it came from, so the UI can always anchor a summary back /// to the real DOM element (§7: a summary without an anchor is not shown). @Generable enum PageKind: String, Codable, Sendable { case article case documentation case forum case boutique case application case portail case formulaire case autre } extension PageKind { /// User-facing label (French, per product conventions). var label: String { switch self { case .article: "Article" case .documentation: "Documentation" case .forum: "Forum" case .boutique: "Boutique" case .application: "Application" case .portail: "Portail" case .formulaire: "Formulaire" case .autre: "Page" } } } @Generable struct DigestSection: Codable, Hashable, Sendable { @Guide(description: "Section heading as it appears in the document") let title: String @Guide(description: "One-sentence summary of the section, based only on the provided text") let summary: String @Guide(description: "Index of the source block where this section starts, from the numbered list") let sourceBlock: Int } @Generable struct DigestClaim: Codable, Hashable, Sendable { @Guide(description: "A dated or numeric factual statement quoted or closely paraphrased from the page") let text: String @Guide(description: "Index of the source block containing this statement") let sourceBlock: Int } @Generable struct PageDigest: Codable, Hashable, Sendable { @Guide(description: "Type of page") let kind: PageKind @Guide(description: "Answer to the page's implicit question, maximum two sentences") let gist: String @Guide(description: "Main sections in document order", .count(1...8)) let outline: [DigestSection] @Guide(description: "Up to five dated or numeric statements from the page", .count(0...5)) let claims: [DigestClaim] } /// Lifecycle of a digest for a given tab. Model refusals and failures are /// normal states with calm copy — the raw page is always available (§7). enum DigestState { case idle case working case ready(PageDigest, blocks: [ContentBlock]) case unavailable(String) case failed(String) }