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// 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