SPB Git

spb/poche Public

Agent personnel 100 % on-device — SwiftUI + Apple Foundation Models + EventKit + SwiftData. Aucune API, aucun serveur.

Swift 100%
4.5 KB · 130 lines swift
Raw Blame History
1//2//  AvailabilityView.swift3//  Poche4//5//  Author:  Simon-Pierre Boucher6//  Contact: contact@spboucher.ai7//89import SwiftUI10import FoundationModels1112/// Unavailability screens. Careful, apologetic, never guilt-tripping,13/// and never offering a replacement LLM (CLAUDE.md §2).14struct AvailabilityView: View {15    let reason: SystemLanguageModel.Availability.UnavailableReason16    let retry: () -> Void1718    var body: some View {19        ZStack {20            AppBackground()2122            VStack(spacing: Theme.spacingLarge) {23                Spacer()2425                ZStack {26                    Circle()27                        .fill(Theme.accentGradient.opacity(0.14))28                        .frame(width: 108, height: 108)29                    Image(systemName: symbolName)30                        .font(.system(size: 44, weight: .light))31                        .foregroundStyle(Theme.accentGradient)32                }3334                Text(title)35                    .font(.title2.weight(.bold))36                    .multilineTextAlignment(.center)37                    .padding(.horizontal, Theme.spacingLarge)3839                Text(message)40                    .font(.body)41                    .foregroundStyle(.secondary)42                    .multilineTextAlignment(.center)43                    .padding(.horizontal, Theme.spacingLarge + 8)4445                actionButton4647                Spacer()4849                HStack(spacing: Theme.spacingSmall) {50                    BrandMark(size: 20)51                    Text("Poche — ton agent, sur ton appareil")52                        .font(.footnote)53                        .foregroundStyle(.secondary)54                }55                .padding(.bottom, Theme.spacingLarge)56            }57        }58        .fontDesign(.rounded)59    }6061    private var symbolName: String {62        switch reason {63        case .deviceNotEligible: "iphone.slash"64        case .appleIntelligenceNotEnabled: "gearshape"65        case .modelNotReady: "arrow.down.circle"66        @unknown default: "questionmark.circle"67        }68    }6970    private var title: String {71        switch reason {72        case .deviceNotEligible:73            "Cet iPhone ne peut pas faire tourner Poche"74        case .appleIntelligenceNotEnabled:75            "Apple Intelligence est désactivé"76        case .modelNotReady:77            "Le modèle se prépare"78        @unknown default:79            "L’intelligence sur l’appareil est indisponible"80        }81    }8283    private var message: String {84        switch reason {85        case .deviceNotEligible:86            "Poche fonctionne entièrement sur l’appareil, sans serveur. Cela demande la puce A17 Pro ou plus récente (iPhone 15 Pro et suivants). Sur cet iPhone, le modèle n’est pas disponible — et Poche ne le remplacera jamais par un service en ligne."87        case .appleIntelligenceNotEnabled:88            "Active Apple Intelligence dans Réglages pour que Poche puisse fonctionner, entièrement sur ton iPhone."89        case .modelNotReady:90            "Le modèle est en cours de téléchargement ou de préparation par le système. Cela peut prendre quelques minutes, notamment après une mise à jour ou en mode économie d’énergie."91        @unknown default:92            "Le modèle sur l’appareil n’est pas disponible pour le moment. Réessaie dans un instant."93        }94    }9596    @ViewBuilder97    private var actionButton: some View {98        switch reason {99        case .deviceNotEligible:100            EmptyView()101        case .appleIntelligenceNotEnabled:102            Button {103                if let url = URL(string: UIApplication.openSettingsURLString) {104                    UIApplication.shared.open(url)105                }106            } label: {107                Text("Ouvrir Réglages")108                    .font(.subheadline.weight(.semibold))109                    .foregroundStyle(.white)110                    .padding(.horizontal, Theme.spacingLarge)111                    .padding(.vertical, Theme.spacingMedium - 2)112                    .background(Theme.accentGradient)113                    .clipShape(Capsule())114            }115            .buttonStyle(.plain)116        default:117            Button(action: retry) {118                Text("Réessayer")119                    .font(.subheadline.weight(.semibold))120                    .foregroundStyle(.white)121                    .padding(.horizontal, Theme.spacingLarge)122                    .padding(.vertical, Theme.spacingMedium - 2)123                    .background(Theme.accentGradient)124                    .clipShape(Capsule())125            }126            .buttonStyle(.plain)127        }128    }129}130