Backend:
- Prisma: add user preferences (dietaryPreference, allergies, maxCookingTime,
equipment, cuisinePreference, servingsDefault) + migration SQL.
Also make stripeId nullable so signup works without Stripe.
- prompts.ts: buildUserPrompt now takes a BuildPromptOptions with preferences.
Injects strong, explicit constraints in the user message (vegan rules,
allergy warnings, time limits, equipment availability, cuisine hints).
- recipe-generator.ts: new streamRecipe() async generator. Streams OpenAI
chat completion with json_schema strict mode, parses the growing buffer
to detect 'titre' and 'description' early, yields typed events:
{ type: 'delta' | 'title' | 'description' | 'complete' }
Final event includes the parsed StructuredRecipe + cost log.
- recipes.ts route: new POST /recipes/create-stream returning SSE:
event: progress { step }
event: transcription{ text }
event: title { title } <- triggers parallel image gen
event: description { description }
event: recipe { recipe }
event: image { url }
event: saved { id }
event: done
Heartbeat every 15s to prevent proxy timeouts. Image generation is
kicked off the moment the title is extracted, running in parallel with
the rest of the recipe stream. Legacy POST /recipes/create still works
and now also passes user preferences.
- users.ts route: GET /profile now returns preferences (equipment
deserialized from JSON). New PUT /users/preferences with validation
(diet enum, time 5-600, servings 1-20, equipment array -> JSON).
- ai.ts plugin: generateRecipe signature extended to accept preferences.
Frontend:
- api/recipe.ts: createRecipeStream() async generator that consumes SSE
via fetch + ReadableStream + TextDecoder (EventSource can't do POST).
Parses 'event:' and 'data:' lines, yields typed StreamEvent union.
- api/auth.ts: User interface extended with preferences; new
UserPreferences type exported.
- api/user.ts: updatePreferences() method.
- RecipeForm.tsx: handleSubmit now consumes the stream. Live UI displays:
1. Initial cooking loader with step label
2. Transcription appears as soon as it's ready
3. Title fades in the moment it's extracted (before the rest of the
recipe finishes generating)
4. Description appears right after
5. Image replaces the loader when ready
6. Small delay before navigating to the saved recipe detail page
- Profile.tsx: new 'Cuisine' tab with form for diet, allergies, max time,
servings, cuisine preference, and equipment checkboxes (8 options).
UX improvement: perceived latency is dramatically reduced. Instead of
waiting 40s staring at a spinner, the user sees the title ~3-4s in and
can start reading, while the image finishes generating in parallel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
52 lines
1.7 KiB
Plaintext
52 lines
1.7 KiB
Plaintext
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "sqlite"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
model User {
|
|
id String @id @default(uuid())
|
|
email String @unique
|
|
password String? // Optionnel pour les utilisateurs Google
|
|
name String
|
|
googleId String? @unique
|
|
stripeId String? @unique // Optionnel : Stripe peut être désactivé
|
|
subscription String?
|
|
resetToken String?
|
|
resetTokenExpiry DateTime?
|
|
|
|
// Préférences culinaires injectées dans le prompt IA
|
|
dietaryPreference String? // 'vegetarian' | 'vegan' | 'pescatarian' | 'none'
|
|
allergies String? // Liste séparée par des virgules : "arachides,gluten"
|
|
maxCookingTime Int? // Temps total max (prépa + cuisson) en minutes
|
|
equipment String? // JSON array : '["four","plaque","micro-ondes"]'
|
|
cuisinePreference String? // "française", "italienne", "asiatique"... ou null
|
|
servingsDefault Int? // Nombre de portions par défaut
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
recipes Recipe[]
|
|
}
|
|
|
|
model Recipe {
|
|
id String @id @default(uuid())
|
|
title String
|
|
ingredients String
|
|
userPrompt String
|
|
generatedRecipe String
|
|
audioUrl String?
|
|
imageUrl String?
|
|
preparationTime Int?
|
|
cookingTime Int?
|
|
servings Int?
|
|
difficulty String?
|
|
steps String?
|
|
tips String?
|
|
userId String
|
|
user User @relation(fields: [userId], references: [id])
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
} |