import Foundation #if canImport(FoundationModels) import FoundationModels #endif /// Splits a single free-form phrase (typically dictated, e.g. "two onions, half /// a litre of milk and some olive oil") into individual, normalized ingredient /// lines. Uses Apple's on-device Foundation Models when available for smart /// parsing of quantities and natural-language separators; otherwise falls back /// to a lightweight heuristic splitter. Fully local and never throws. enum IngredientParser { /// Parses `text` into clean ingredient lines, kept in `language`. static func parse(_ text: String, language: AppLanguage) async -> [String] { let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines) guard !trimmed.isEmpty else { return [] } #if canImport(FoundationModels) if #available(iOS 26.0, *), case .available = SystemLanguageModel.default.availability { let langName = language.resolved().displayName let instructions = """ You split a spoken grocery list into individual ingredients. The user \ dictates several ingredients in one sentence, separated by commas, the \ word "and", or natural pauses. Output ONE ingredient per line, keeping \ any quantity the user actually said (e.g. "2 onions", "500 g flour"). \ Do not invent ingredients, do not add quantities that were not said, \ do not number or bullet the lines, and add no commentary. Keep the \ ingredients written in \(langName). """ do { let session = LanguageModelSession(instructions: instructions) let response = try await session.respond(to: trimmed) let lines = IngredientGenerator.parseLines(response.content) if !lines.isEmpty { return lines } } catch { CrashlyticsService.record(error, context: "ingredient_parsing") } } #endif return heuristicSplit(trimmed) } /// Splits on newlines, commas, semicolons and standalone conjunctions /// ("and"/"y"/"e"/"et"/"und") when the on-device model is unavailable. static func heuristicSplit(_ text: String) -> [String] { let separators = CharacterSet(charactersIn: ",;\n•") let conjunctions = [" y ", " e ", " and ", " et ", " und "] return text .components(separatedBy: separators) .flatMap { piece -> [String] in var parts = [piece] for conjunction in conjunctions { parts = parts.flatMap { $0.components(separatedBy: conjunction) } } return parts } .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } .filter { !$0.isEmpty } } }