From 03727ee3c5f5a64e1146a11d5fa7a555b6c1c146 Mon Sep 17 00:00:00 2001 From: Nils-Johan Gynther Date: Sun, 12 Apr 2026 10:06:51 +0200 Subject: [PATCH] feat: Implement auto-parsing of markdown in WriteRecipePage with error handling --- .../src/quick-import/parsers/base.parser.ts | 44 ++++++++++++++++-- .../app/recipes/write/WriteRecipePage.tsx | 45 ++++++++++++++++--- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/backend/src/quick-import/parsers/base.parser.ts b/backend/src/quick-import/parsers/base.parser.ts index bcda49d8..bfca6265 100644 --- a/backend/src/quick-import/parsers/base.parser.ts +++ b/backend/src/quick-import/parsers/base.parser.ts @@ -26,6 +26,11 @@ export abstract class RecipeParser { /** * Hjälpfunktion: parsa ingrediens-rad + * Hanterar format som: + * - "3 ägg" + * - "150 g lax" + * - "1 msk senap" + * - "salt och peppar" */ protected parseIngredientLine(line: string): { quantity: number; @@ -35,7 +40,17 @@ export abstract class RecipeParser { const cleaned = line.replace(/<[^>]+>/g, '').trim(); if (!cleaned) return null; - const match = cleaned.match(/^([\d.,]+)?\s*([a-zåäö]*)\s*(.+)$/i); + // Kända enheter + const knownUnits = [ + 'g', 'kg', 'hg', 'ml', 'dl', 'l', + 'st', 'tsk', 'msk', 'krm', 'matsked', 'tesked', + 'pris', 'portion', 'burk', 'förp', 'paket', + ]; + + // Försök extrahera: [quantity] [unit?] [productName] + // Regex: början med valfri mängd, sedan valfritt ord (potentiell enhet), sedan resten + const match = cleaned.match(/^([\d.,]+)?\s*([a-zåäö]*)\s*(.*)$/i); + if (!match) { return { quantity: 0, @@ -44,10 +59,31 @@ export abstract class RecipeParser { }; } + let quantity = match[1] ? parseFloat(match[1].replace(',', '.')) : 0; + let potentialUnit = match[2]?.toLowerCase().trim() || ''; + let productName = match[3]?.trim() || ''; + + // Om potentialUnit är inte en känd enhet, lägg det tillbaka i produktnamnet + if (potentialUnit && !knownUnits.includes(potentialUnit)) { + productName = potentialUnit + (productName ? ' ' + productName : ''); + potentialUnit = ''; + } + + // Om inget produktnamn men vi hade potentialUnit, denna är faktiskt produktnamnet + if (!productName && potentialUnit) { + productName = potentialUnit; + potentialUnit = ''; + } + + // Fallback: om vi har mängd men inget annat, vara produktnamn + if (quantity > 0 && !potentialUnit && !productName) { + return null; // Ogiltigt + } + return { - quantity: match[1] ? parseFloat(match[1].replace(',', '.')) : 0, - unit: (match[2] || 'st').toLowerCase().trim(), - name: match[3].trim(), + quantity, + unit: potentialUnit || 'st', + name: productName, }; } } diff --git a/frontend/app/recipes/write/WriteRecipePage.tsx b/frontend/app/recipes/write/WriteRecipePage.tsx index d85213d8..a8011672 100644 --- a/frontend/app/recipes/write/WriteRecipePage.tsx +++ b/frontend/app/recipes/write/WriteRecipePage.tsx @@ -72,11 +72,46 @@ export default function WriteRecipePage() { if (prefilledMarkdown) { setMarkdown(prefilledMarkdown); sessionStorage.removeItem('prefilled_markdown'); - // Auto-parse om Markdown finns - setTimeout(() => { - // Markeringen för auto-parse görs via en flag - sessionStorage.setItem('auto_parse_markdown', 'true'); - }, 100); + // Auto-parse markdown från snabbimport + setIsParsing(true); + (async () => { + try { + const res = await fetch('/api/parse-markdown-proxy', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ markdown: prefilledMarkdown }), + }); + + if (!res.ok) { + const errorText = await res.text(); + throw new Error(errorText || 'Kunde inte tolka recept'); + } + + const data = await res.json(); + + const rows: ParsedIngredientRow[] = data.ingredients.map( + (ing: Omit) => ({ + ...ing, + selectedProductId: ing.suggestions[0]?.productId ?? 0, + editedQuantity: String(ing.quantity), + editedUnit: ing.unit, + editedNote: ing.note ?? '', + }), + ); + + setParsed(data); + setEditedName(data.name); + setEditedDescription(data.description ?? ''); + setEditedInstructions(data.instructions ?? ''); + setIngredients(rows); + setStep('review'); + } catch (err) { + const message = err instanceof Error ? err.message : 'Något gick fel vid tolkning.'; + setError(message); + } finally { + setIsParsing(false); + } + })(); } }, []);