feat(ai): Vercel AI SDK provider factory with BYOK and per-use-case model prefs
Provider factory (OpenAI/Anthropic/OpenRouter/Ollama). generateObject for all outputs. Features: recipe generation, photo-to-recipe vision, variations, drink/meal pairings, ingredient substitution, recipe adaptation, nutrition analysis, URL import, meal plan gen. User BYOK keys (AES-256-GCM). Per-use-case model preferences (text/vision/mealPlan). Site-level key override via admin settings.
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { generateObject } from "ai";
|
||||
import { z } from "zod";
|
||||
import { resolveModel, type AiConfig } from "../factory";
|
||||
|
||||
const TranslationOutputSchema = z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
ingredients: z.array(z.object({
|
||||
rawName: z.string(),
|
||||
note: z.string().optional(),
|
||||
})),
|
||||
steps: z.array(z.object({
|
||||
instruction: z.string(),
|
||||
})),
|
||||
});
|
||||
|
||||
export type TranslatedRecipe = z.infer<typeof TranslationOutputSchema>;
|
||||
|
||||
export async function translateRecipe(
|
||||
recipe: {
|
||||
title: string;
|
||||
description?: string | null;
|
||||
ingredients: Array<{ rawName: string; note?: string | null }>;
|
||||
steps: Array<{ instruction: string }>;
|
||||
},
|
||||
targetLanguage: string,
|
||||
config?: AiConfig
|
||||
): Promise<TranslatedRecipe> {
|
||||
const model = resolveModel(config);
|
||||
|
||||
const { object } = await generateObject({
|
||||
model,
|
||||
schema: TranslationOutputSchema,
|
||||
system:
|
||||
"You are a professional culinary translator. Translate recipes accurately, preserving culinary meaning, cooking terms, and cultural context. Keep ingredient quantities and units as-is (numbers and unit abbreviations are universal). Only translate text content.",
|
||||
prompt: `Translate the following recipe into ${targetLanguage}. Return the same structure with translated text.\n\nTitle: ${recipe.title}\nDescription: ${recipe.description ?? ""}\nIngredients: ${recipe.ingredients.map((i) => `- ${i.rawName}${i.note ? ` (${i.note})` : ""}`).join("\n")}\nSteps: ${recipe.steps.map((s, i) => `${i + 1}. ${s.instruction}`).join("\n")}`,
|
||||
});
|
||||
|
||||
return object;
|
||||
}
|
||||
Reference in New Issue
Block a user