SPB Git

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%
4.0 KB · 126 lines swift
Raw Blame History
1//2//  IntentResolver.swift3//  Prisme4//5//  Author: Simon-Pierre Boucher <contact@spboucher.ai>6//78import Foundation910/// A search engine the user can route a query through. DuckDuckGo is the11/// default (privacy thesis, CLAUDE.md §1); Google is offered as an explicit12/// alternative on every query — the user always sees where a search goes.13enum SearchEngine: String, CaseIterable, Codable, Sendable, Identifiable {14    case duckDuckGo15    case google1617    var id: String { rawValue }1819    var name: String {20        switch self {21        case .duckDuckGo: "DuckDuckGo"22        case .google: "Google"23        }24    }2526    var symbol: String {27        switch self {28        case .duckDuckGo: "shield.lefthalf.filled"29        case .google: "g.circle.fill"30        }31    }3233    var other: SearchEngine {34        self == .duckDuckGo ? .google : .duckDuckGo35    }3637    func searchURL(for query: String) -> URL {38        let encoded = query.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? query39        switch self {40        case .duckDuckGo:41            return URL(string: "https://duckduckgo.com/?q=\(encoded)")!42        case .google:43            return URL(string: "https://www.google.com/search?q=\(encoded)")!44        }45    }46}4748/// What the user meant when typing in the address bar.49/// Phase 1 distinguishes URL vs search with pure heuristics (tier `none`,50/// CLAUDE.md §3) — question and command tiers arrive with the intelligence51/// layer. The bar never guesses silently: proposals are shown and the user52/// confirms one.53enum EntryIntent: Hashable {54    case navigate(URL)55    case search(String, SearchEngine)5657    /// User-facing label (French, per product conventions).58    var label: String {59        switch self {60        case .navigate(let url):61            "Aller sur \(url.host() ?? url.absoluteString)"62        case .search(let query, _):63            "Rechercher « \(query) »"64        }65    }6667    /// Secondary line: where the action goes.68    var detail: String {69        switch self {70        case .navigate: "Adresse directe"71        case .search(_, let engine): engine.name72        }73    }7475    var symbol: String {76        switch self {77        case .navigate: "globe"78        case .search(_, let engine): engine.symbol79        }80    }8182    /// URL to actually load.83    var destination: URL {84        switch self {85        case .navigate(let url):86            url87        case .search(let query, let engine):88            engine.searchURL(for: query)89        }90    }91}9293/// Pure-heuristic resolver: zero AI, zero network, deterministic.94enum IntentResolver {95    /// Ordered proposals for the given input, most likely first. The96    /// preferred engine leads; the other engine is always one tap away.97    static func propose(for input: String, preferring engine: SearchEngine) -> [EntryIntent] {98        let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines)99        guard !trimmed.isEmpty else { return [] }100101        if let url = explicitURL(from: trimmed) ?? implicitURL(from: trimmed) {102            return [.navigate(url), .search(trimmed, engine), .search(trimmed, engine.other)]103        }104        return [.search(trimmed, engine), .search(trimmed, engine.other)]105    }106107    /// Input carrying its own scheme, e.g. "https://example.com".108    private static func explicitURL(from input: String) -> URL? {109        guard input.lowercased().hasPrefix("http://") || input.lowercased().hasPrefix("https://"),110              let url = URL(string: input), url.host() != nil111        else { return nil }112        return url113    }114115    /// Bare host-like input, e.g. "example.com/page" or "localhost:3000".116    private static func implicitURL(from input: String) -> URL? {117        guard !input.contains(" ") else { return nil }118        let hostPart = input.split(separator: "/").first.map(String.init) ?? input119        let looksLikeHost = hostPart.contains(".") || hostPart.hasPrefix("localhost")120        guard looksLikeHost, let url = URL(string: "https://\(input)"), url.host() != nil else {121            return nil122        }123        return url124    }125}126