feat: meal tracking, dish recognition UX improvements, English AI prompts
Backend: - Translate all recognition prompts (receipt, products, dish) from Russian to English - Add lang parameter to Recognizer interface and pass locale.FromContext in handlers - DishResult type uses candidates array for multi-candidate responses Client: - Add meal tracking: diary provider, date selector, meal type model - DishResult parser: backward-compatible with legacy flat format and new candidates format - DishResultScreen: sticky bottom button, full-width portion/meal-type inputs, КБЖУ disclaimer moved under nutrition card, add date field to diary POST body - Recognition prompts now return dish/product names in user's preferred language - Onboarding, profile, home screen visual updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/food-ai/backend/internal/adapters/ai"
|
||||
"github.com/food-ai/backend/internal/infra/locale"
|
||||
"github.com/food-ai/backend/internal/infra/middleware"
|
||||
"github.com/food-ai/backend/internal/domain/ingredient"
|
||||
)
|
||||
@@ -23,9 +24,9 @@ type IngredientRepository interface {
|
||||
|
||||
// Recognizer is the AI provider interface for image-based food recognition.
|
||||
type Recognizer interface {
|
||||
RecognizeReceipt(ctx context.Context, imageBase64, mimeType string) (*ai.ReceiptResult, error)
|
||||
RecognizeProducts(ctx context.Context, imageBase64, mimeType string) ([]ai.RecognizedItem, error)
|
||||
RecognizeDish(ctx context.Context, imageBase64, mimeType string) (*ai.DishResult, error)
|
||||
RecognizeReceipt(ctx context.Context, imageBase64, mimeType, lang string) (*ai.ReceiptResult, error)
|
||||
RecognizeProducts(ctx context.Context, imageBase64, mimeType, lang string) ([]ai.RecognizedItem, error)
|
||||
RecognizeDish(ctx context.Context, imageBase64, mimeType, lang string) (*ai.DishResult, error)
|
||||
ClassifyIngredient(ctx context.Context, name string) (*ai.IngredientClassification, error)
|
||||
}
|
||||
|
||||
@@ -91,7 +92,8 @@ func (h *Handler) RecognizeReceipt(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.recognizer.RecognizeReceipt(r.Context(), req.ImageBase64, req.MimeType)
|
||||
lang := locale.FromContext(r.Context())
|
||||
result, err := h.recognizer.RecognizeReceipt(r.Context(), req.ImageBase64, req.MimeType, lang)
|
||||
if err != nil {
|
||||
slog.Error("recognize receipt", "err", err)
|
||||
writeErrorJSON(w, http.StatusServiceUnavailable, "recognition failed, please try again")
|
||||
@@ -118,13 +120,14 @@ func (h *Handler) RecognizeProducts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Process each image in parallel.
|
||||
lang := locale.FromContext(r.Context())
|
||||
allItems := make([][]ai.RecognizedItem, len(req.Images))
|
||||
var wg sync.WaitGroup
|
||||
for i, img := range req.Images {
|
||||
wg.Add(1)
|
||||
go func(i int, img imageRequest) {
|
||||
defer wg.Done()
|
||||
items, err := h.recognizer.RecognizeProducts(r.Context(), img.ImageBase64, img.MimeType)
|
||||
items, err := h.recognizer.RecognizeProducts(r.Context(), img.ImageBase64, img.MimeType, lang)
|
||||
if err != nil {
|
||||
slog.Warn("recognize products from image", "index", i, "err", err)
|
||||
return
|
||||
@@ -148,7 +151,8 @@ func (h *Handler) RecognizeDish(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.recognizer.RecognizeDish(r.Context(), req.ImageBase64, req.MimeType)
|
||||
lang := locale.FromContext(r.Context())
|
||||
result, err := h.recognizer.RecognizeDish(r.Context(), req.ImageBase64, req.MimeType, lang)
|
||||
if err != nil {
|
||||
slog.Error("recognize dish", "err", err)
|
||||
writeErrorJSON(w, http.StatusServiceUnavailable, "recognition failed, please try again")
|
||||
|
||||
@@ -112,7 +112,7 @@ func buildUpdateQuery(id string, req UpdateProfileRequest) (string, []interface{
|
||||
argIdx++
|
||||
}
|
||||
if req.Preferences != nil {
|
||||
setClauses = append(setClauses, fmt.Sprintf("preferences = $%d", argIdx))
|
||||
setClauses = append(setClauses, fmt.Sprintf("preferences = preferences || $%d::jsonb", argIdx))
|
||||
args = append(args, string(*req.Preferences))
|
||||
argIdx++
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user