import { z } from 'zod'; import { SchemaType } from '@google/generative-ai'; export const RECIPE_EXTRACTION_SYSTEM_PROMPT = `Du bist ein hochpräziser OCR-Experte für kulinarische Dokumente (Rezepte). Deine Aufgabe ist die Extraktion von Rezeptdaten (Titel, Zutaten, Zubereitungsschritte, Zeiten, Portionen) in valides JSON gemäß dem vorgegebenen Schema. SPRACHE: - Die Texte sind ausschließlich auf Deutsch. Nutze deutsches Sprachverständnis (Umlaute ä/ö/ü/ß, deutsche Zutatennamen, deutsche Maßeinheiten) als starken Prior bei der Rekonstruktion unklarer Zeichen. Gib die Ausgabe vollständig auf Deutsch zurück. LOGIK-REGELN FÜR SCHWER LESBARE TEXTE: - Handle als "Kontext-Detektiv": Wenn Zeichen unklar sind, nutze kulinarisches Wissen zur Rekonstruktion (z.B. "Pr-se" -> "Prise"). - Bei absoluter Unleserlichkeit eines Wortes: Nutze "[?]". - Halluziniere keine fehlenden Werte: Wenn eine Mengenangabe komplett fehlt, setze 'quantity' auf null. Was nicht auf dem Bild steht, ist null (oder leeres Array). FORMATIERUNGS-REGELN: - Zutaten: quantity (Zahl) separat von unit (String). Brüche (½, ¼, 1 ½) strikt in Dezimalzahlen (0.5, 0.25, 1.5). - Einheiten: Normalisiere auf (g, ml, l, kg, EL, TL, Stück, Prise, Msp). - Zubereitungsschritte: pro erkennbarer Nummerierung oder Absatz EIN Schritt. - Zeit: Alle Angaben strikt in Minuten (Integer). "1 Stunde" = 60. - Rauschen ignorieren: Keine Werbung, Einleitungstexte oder Bildunterschriften extrahieren. STRIKTE ANWEISUNG: Gib ausschließlich das rohe JSON-Objekt gemäß Schema zurück. Kein Markdown-Code-Block, kein Einleitungstext, keine Prosa.`; export const RECIPE_EXTRACTION_USER_PROMPT = 'Analysiere dieses Bild hochauflösend. Extrahiere alle rezeptrelevanten Informationen gemäß deiner System-Instruktion. Achte besonders auf schwache Handschriften oder verblassten Text und stelle sicher, dass die Zuordnung von Menge zu Zutat logisch korrekt ist.'; // Gemini responseSchema (Subset von OpenAPI). Wird an GenerativeModel.generateContent // übergeben; Gemini respektiert die Struktur und liefert valides JSON. export const GEMINI_RESPONSE_SCHEMA = { type: SchemaType.OBJECT, properties: { title: { type: SchemaType.STRING, nullable: false }, servings_default: { type: SchemaType.INTEGER, nullable: true }, servings_unit: { type: SchemaType.STRING, nullable: true }, prep_time_min: { type: SchemaType.INTEGER, nullable: true }, cook_time_min: { type: SchemaType.INTEGER, nullable: true }, total_time_min: { type: SchemaType.INTEGER, nullable: true }, ingredients: { type: SchemaType.ARRAY, items: { type: SchemaType.OBJECT, properties: { quantity: { type: SchemaType.NUMBER, nullable: true }, unit: { type: SchemaType.STRING, nullable: true }, name: { type: SchemaType.STRING, nullable: false }, note: { type: SchemaType.STRING, nullable: true } }, required: ['name'] } }, steps: { type: SchemaType.ARRAY, items: { type: SchemaType.OBJECT, properties: { text: { type: SchemaType.STRING, nullable: false } }, required: ['text'] } } }, required: ['title', 'ingredients', 'steps'] } as const; // Zod-Spiegel des Schemas. .strict() verhindert, dass Gemini zusätzliche Keys // unbemerkt durchschmuggelt. const ingredientSchema = z .object({ quantity: z.number().nullable(), unit: z.string().max(30).nullable(), name: z.string().min(1).max(200), note: z.string().max(300).nullable() }) .strict(); const stepSchema = z .object({ text: z.string().min(1).max(4000) }) .strict(); export const extractionResponseSchema = z .object({ title: z.string().min(1).max(200), servings_default: z.number().int().nonnegative().nullable(), servings_unit: z.string().max(30).nullable(), prep_time_min: z.number().int().nonnegative().nullable(), cook_time_min: z.number().int().nonnegative().nullable(), total_time_min: z.number().int().nonnegative().nullable(), ingredients: z.array(ingredientSchema), steps: z.array(stepSchema) }) .strict(); export type ExtractionResponse = z.infer;