Files
Arnaud f0340859fa feat: split photo-import into vision recognition + text generation
The photo-import flow used one vision-capable model to both read the
photo and structure the full recipe (quantities, steps, timing) in a
single call. Split it into two: a vision model recognizes what's in
the picture, then a text model reconstructs the recipe from that
description — same pattern the pantry photo-scan already uses for
recognition, and lets structuring use whichever model is actually
configured for text generation. Still counts as one AI-quota unit.

v0.37.0
2026-07-17 16:12:39 +02:00

31 lines
1.5 KiB
TypeScript

import type { AiConfig } from "../factory";
import { recognizePhoto } from "./recognize-photo";
import { generateRecipeFromRecognition, type GeneratedRecipeFromRecognition } from "./generate-recipe-from-recognition";
export type ImportedRecipe =
| { found: false; recipeType: "dish" | "drink"; title: string; ingredients: []; steps: [] }
| ({ found: true; recipeType: "dish" | "drink" } & GeneratedRecipeFromRecognition);
/** Orchestrates the two-step photo-import flow: a vision model recognizes
* what's in the photo (`recognizePhoto`), then a text model reconstructs the
* full structured recipe from that recognition (`generateRecipeFromRecognition`).
* Splitting the call lets each step use the model best suited to it — vision
* for the photo, text for structuring — instead of one model doing both. */
export async function importFromPhoto(
imageBase64: string,
mimeType: "image/jpeg" | "image/png" | "image/webp",
visionConfig: AiConfig | undefined,
textConfig: AiConfig | undefined,
locale?: string,
): Promise<ImportedRecipe> {
const recognition = await recognizePhoto(imageBase64, mimeType, visionConfig);
if (!recognition.found) {
return { found: false, recipeType: recognition.recipeType, title: recognition.title, ingredients: [], steps: [] };
}
const generated = await generateRecipeFromRecognition(recognition, { ...textConfig, language: locale ?? "en" });
return { found: true, recipeType: recognition.recipeType, ...generated };
}