prompt( $this->prompt(), [$request->file('image')], timeout: 90, ); } catch (Throwable $exception) { report($exception); return response()->json([ 'message' => __('api.meal_image_analysis.failed'), ], 502); } return response()->json([ 'data' => $this->normalizeDraft( $response instanceof Arrayable ? $response->toArray() : (json_decode($response->text, true) ?: []) ), ]); } private function prompt(): string { return <<<'PROMPT' Analyse l'image du repas et prépare un brouillon éditable pour l'utilisateur. Contraintes: - Ne renseigne que ce qui est visible ou très probable dans l'image. - Estime les quantités pour une portion consommable. - Retourne des calories et macros cohérentes entre elles. - Si l'image ne montre pas clairement un repas, retourne un titre générique et des valeurs à 0 avec une liste d'ingrédients vide. PROMPT; } private function normalizeDraft(array $draft): array { $type = MealPostType::tryFrom($this->cleanString($draft['type'] ?? '', 32)) ?? MealPostType::OTHER; return [ 'title' => $this->cleanString($draft['title'] ?? 'Repas', 255) ?: 'Repas', 'caption' => $this->cleanString($draft['caption'] ?? '', 1000), 'type' => $type->value, 'calories' => $this->positiveInteger($draft['calories'] ?? 0), 'proteins' => $this->positiveNumber($draft['proteins'] ?? 0), 'carbs' => $this->positiveNumber($draft['carbs'] ?? 0), 'fats' => $this->positiveNumber($draft['fats'] ?? 0), 'ingredients' => $this->normalizeIngredients($draft['ingredients'] ?? []), ]; } private function normalizeIngredients(mixed $ingredients): array { if (! is_array($ingredients)) { return []; } $normalized = []; foreach (array_slice($ingredients, 0, 12) as $ingredient) { if (! is_array($ingredient)) { continue; } $name = $this->cleanString($ingredient['ingredient'] ?? '', 255); if ($name === '') { continue; } $unit = IngredientUnit::tryFrom($this->cleanString($ingredient['unit'] ?? '', 16)) ?? IngredientUnit::GRAM; $normalized[] = [ 'position' => count($normalized) + 1, 'ingredient' => $name, 'quantity' => max(1, $this->positiveInteger($ingredient['quantity'] ?? 1)), 'unit' => $unit->value, ]; } return $normalized; } private function cleanString(mixed $value, int $limit): string { if (! is_string($value) && ! is_numeric($value)) { return ''; } $cleaned = trim((string) preg_replace('/\s+/', ' ', (string) $value)); return Str::limit($cleaned, $limit, ''); } private function positiveInteger(mixed $value): int { if (! is_numeric($value)) { return 0; } return max(0, (int) round((float) $value)); } private function positiveNumber(mixed $value): float { if (! is_numeric($value)) { return 0; } return round(max(0, (float) $value), 1); } }