package openai import ( "context" "encoding/json" "fmt" "strings" "github.com/food-ai/backend/internal/adapters/ai" "github.com/food-ai/backend/internal/infra/locale" ) // GenerateRecipeForDish generates a single English recipe for a named dish. func (c *Client) GenerateRecipeForDish(ctx context.Context, dishName string) (*ai.Recipe, error) { prompt := fmt.Sprintf(`You are a chef and nutritionist. Generate exactly 1 recipe for the dish "%s" in English. Requirements: - Include accurate macros per serving - Total cooking time: max 60 minutes IMPORTANT: - All text fields MUST be in English. - The "image_query" field MUST be in English. Return ONLY a valid JSON array with exactly one element, no markdown: [{"title":"...","description":"...","cuisine":"russian|asian|european|mediterranean|american|other","difficulty":"easy|medium|hard","prep_time_min":10,"cook_time_min":20,"servings":2,"image_query":"...","ingredients":[{"name":"...","amount":100,"unit":"..."}],"steps":[{"number":1,"description":"...","timer_seconds":null}],"tags":["..."],"nutrition_per_serving":{"calories":400,"protein_g":30,"fat_g":10,"carbs_g":40}}]`, dishName) var lastError error for attempt := range maxRetries { messages := []map[string]string{{"role": "user", "content": prompt}} if attempt > 0 { messages = append(messages, map[string]string{ "role": "user", "content": "Previous response was not valid JSON. Return ONLY a JSON array with no text before or after.", }) } text, generateError := c.generateContent(ctx, messages) if generateError != nil { return nil, generateError } recipes, parseError := parseRecipesJSON(text) if parseError != nil { lastError = parseError continue } if len(recipes) == 0 { lastError = fmt.Errorf("empty recipes array") continue } recipes[0].Nutrition.Approximate = true return &recipes[0], nil } return nil, fmt.Errorf("generate recipe for dish %q after %d attempts: %w", dishName, maxRetries, lastError) } // TranslateDishName translates a dish name into all supported languages (except English) // in a single AI call. Returns a map of lang-code → translated name. func (c *Client) TranslateDishName(ctx context.Context, name string) (map[string]string, error) { var langLines []string for _, language := range locale.Languages { if language.Code != "en" { langLines = append(langLines, fmt.Sprintf(`"%s": "%s"`, language.Code, language.EnglishName)) } } if len(langLines) == 0 { return map[string]string{}, nil } prompt := fmt.Sprintf(`Translate the dish name "%s" into the following languages. Return ONLY a valid JSON object mapping language code to translated name, no markdown: {%s} Fill each value with the natural translation of the dish name in that language.`, name, strings.Join(langLines, ", ")) text, generateError := c.generateContent(ctx, []map[string]string{ {"role": "user", "content": prompt}, }) if generateError != nil { return nil, generateError } text = strings.TrimSpace(text) if strings.HasPrefix(text, "```") { text = strings.TrimPrefix(text, "```json") text = strings.TrimPrefix(text, "```") text = strings.TrimSuffix(text, "```") text = strings.TrimSpace(text) } var translations map[string]string if parseError := json.Unmarshal([]byte(text), &translations); parseError != nil { return nil, fmt.Errorf("parse translations for %q: %w", name, parseError) } return translations, nil }