// // OmniIntent.swift // Zyquo Atlas // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Resolves omnibox input into a navigable URL: direct navigation vs. web // search. This is the Phase 2 URL-vs-search half of the smart omnibox; the // Phase 3 "ask AI" intent (docs/AI-BROWSER-RESEARCH.md ยง4.1) layers on top as a // third route with explicit affordances โ€” deliberately not auto-hijacking // navigate/search here. // import Foundation enum OmniIntent: Equatable { /// Navigate directly to this URL. case navigate(URL) /// Run a web search for this query. case search(String) /// The default search engine query template (`%@` replaced by the escaped query). static let searchTemplate = "https://duckduckgo.com/?q=%@" /// Classify raw omnibox text. Bias toward navigate/search (reversible, /// cheap) per the research; the AI "ask" route is opt-in elsewhere. static func resolve(_ raw: String) -> OmniIntent { let text = raw.trimmingCharacters(in: .whitespacesAndNewlines) guard !text.isEmpty else { return .search("") } // Explicit scheme (http/https/file/about/data) โ†’ navigate. if let url = URL(string: text), let scheme = url.scheme?.lowercased(), ["http", "https", "file", "about", "data"].contains(scheme) { return .navigate(url) } // localhost / IP[:port] / bare host with a known-looking TLD and no // spaces โ†’ treat as a URL, defaulting to https. if !text.contains(" "), looksLikeHost(text) { if let url = URL(string: "https://\(text)") { return .navigate(url) } } return .search(text) } /// The URL to actually load for this intent. var url: URL? { switch self { case .navigate(let url): return url case .search(let query): let escaped = query.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed ) ?? query return URL(string: String(format: OmniIntent.searchTemplate, escaped)) } } // MARK: - Heuristics private static func looksLikeHost(_ text: String) -> Bool { let host = text.split(separator: "/", maxSplits: 1).first.map(String.init) ?? text let bare = host.split(separator: ":", maxSplits: 1).first.map(String.init) ?? host if bare == "localhost" { return true } if isIPAddress(bare) { return true } // host.tld shape: at least one dot, a plausible TLD, no whitespace. guard let lastDot = bare.lastIndex(of: "."), lastDot != bare.startIndex else { return false } let tld = bare[bare.index(after: lastDot)...] return tld.count >= 2 && tld.allSatisfy { $0.isLetter } } private static func isIPAddress(_ s: String) -> Bool { let parts = s.split(separator: ".") guard parts.count == 4 else { return false } return parts.allSatisfy { part in guard let n = Int(part) else { return false } return (0...255).contains(n) } } }