@usamir/healthy-meals-core 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,20 +10,25 @@ export declare class AIMealPlanGenerator {
10
10
  existingMeals?: string[];
11
11
  avoidIngredients?: string[];
12
12
  focusAreas?: string[];
13
+ language?: string;
13
14
  }): Promise<MealPlan>;
14
15
  generateWeeklyMealPlan(profile: UserProfile, userId: string, options?: {
15
16
  existingMeals?: string[];
16
17
  avoidIngredients?: string[];
17
18
  focusAreas?: string[];
19
+ language?: string;
18
20
  }): Promise<MealPlan>;
19
21
  generateCustomRecipe(profile: UserProfile, mealType: 'breakfast' | 'lunch' | 'dinner' | 'snack', options?: {
20
22
  ingredients?: string[];
21
23
  cookingTime?: number;
22
24
  difficulty?: 'easy' | 'medium' | 'hard';
23
25
  servings?: number;
26
+ language?: string;
24
27
  }): Promise<Meal>;
25
28
  suggestMealVariations(meal: Meal, profile: UserProfile): Promise<Meal[]>;
26
- analyzeMealPlan(mealPlan: MealPlan, profile: UserProfile): Promise<{
29
+ analyzeMealPlan(mealPlan: MealPlan, profile: UserProfile, options?: {
30
+ language?: string;
31
+ }): Promise<{
27
32
  nutritionalAnalysis: string;
28
33
  recommendations: string[];
29
34
  improvements: string[];
@@ -124,7 +124,7 @@ class AIMealPlanGenerator {
124
124
  avoidIngredients: options?.avoidIngredients,
125
125
  focusAreas: options?.focusAreas
126
126
  };
127
- const response = await this.aiService.generateMealPlan(request);
127
+ const response = await this.aiService.generateMealPlan(request, options?.language || 'en');
128
128
  if (!response.success || !response.data) {
129
129
  throw new Error(`Failed to generate AI meal plan: ${response.error}`);
130
130
  }
@@ -139,7 +139,7 @@ class AIMealPlanGenerator {
139
139
  avoidIngredients: options?.avoidIngredients,
140
140
  focusAreas: options?.focusAreas
141
141
  };
142
- const response = await this.aiService.generateMealPlan(request);
142
+ const response = await this.aiService.generateMealPlan(request, options?.language || 'en');
143
143
  if (!response.success || !response.data) {
144
144
  throw new Error(`Failed to generate AI weekly meal plan: ${response.error}`);
145
145
  }
@@ -155,7 +155,7 @@ class AIMealPlanGenerator {
155
155
  difficulty: options?.difficulty,
156
156
  servings: options?.servings
157
157
  };
158
- const response = await this.aiService.generateRecipe(request);
158
+ const response = await this.aiService.generateRecipe(request, options?.language || 'en');
159
159
  if (!response.success || !response.data) {
160
160
  throw new Error(`Failed to generate AI recipe: ${response.error}`);
161
161
  }
@@ -196,7 +196,7 @@ class AIMealPlanGenerator {
196
196
  }
197
197
  return response.data.map(variation => this.convertAIMealToMeal(variation));
198
198
  }
199
- async analyzeMealPlan(mealPlan, profile) {
199
+ async analyzeMealPlan(mealPlan, profile, options) {
200
200
  const context = this.convertUserProfileToAIContext(profile);
201
201
  // Convert meals to AI format
202
202
  const aiMeals = (mealPlan.meals || []).map(meal => ({
@@ -225,7 +225,7 @@ class AIMealPlanGenerator {
225
225
  tags: meal.tags,
226
226
  tips: meal.tips
227
227
  }));
228
- const response = await this.aiService.analyzeMealPlan(aiMeals, context);
228
+ const response = await this.aiService.analyzeMealPlan(aiMeals, context, options?.language || 'en');
229
229
  if (!response.success || !response.data) {
230
230
  throw new Error(`Failed to analyze meal plan: ${response.error}`);
231
231
  }
@@ -11,13 +11,16 @@ export declare class UnifiedAIService implements AIServiceInterface {
11
11
  constructor(config: AIConfig);
12
12
  private makeRequest;
13
13
  private extractResponseContent;
14
+ private translateHealthGoals;
15
+ private translateActivityLevel;
16
+ private translateDietaryRestrictions;
14
17
  private buildUserProfileContext;
15
18
  private parseJSONResponse;
16
19
  private fixCommonJSONIssues;
17
- generateMealPlan(request: AIMealPlanRequest): Promise<AIResponse<AIGeneratedMealPlan>>;
18
- generateRecipe(request: AIRecipeRequest): Promise<AIResponse<AIGeneratedMeal>>;
20
+ generateMealPlan(request: AIMealPlanRequest, language?: string): Promise<AIResponse<AIGeneratedMealPlan>>;
21
+ generateRecipe(request: AIRecipeRequest, language?: string): Promise<AIResponse<AIGeneratedMeal>>;
19
22
  suggestMealVariations(meal: AIGeneratedMeal, context: AIPromptContext): Promise<AIResponse<AIGeneratedMeal[]>>;
20
- analyzeMealPlan(meals: AIGeneratedMeal[], context: AIPromptContext): Promise<AIResponse<{
23
+ analyzeMealPlan(meals: AIGeneratedMeal[], context: AIPromptContext, language?: string): Promise<AIResponse<{
21
24
  nutritionalAnalysis: string;
22
25
  recommendations: string[];
23
26
  improvements: string[];
@@ -15,6 +15,8 @@ class UnifiedAIService {
15
15
  let headers = {
16
16
  'Content-Type': 'application/json',
17
17
  };
18
+ console.log('AI Config - baseUrl:', this.config.baseUrl);
19
+ console.log('AI Config - isGroq:', this.isGroq);
18
20
  if (this.isGroq) {
19
21
  // Groq/OpenAI API format
20
22
  requestBody = {
@@ -27,7 +29,8 @@ class UnifiedAIService {
27
29
  max_tokens: 4000,
28
30
  stream: false
29
31
  };
30
- endpoint = '/chat/completions';
32
+ endpoint = this.isGroq ? '/chat/completions' : '/api/generate';
33
+ console.log('AI Config - endpoint:', endpoint);
31
34
  if (this.config.apiKey) {
32
35
  headers['Authorization'] = `Bearer ${this.config.apiKey}`;
33
36
  }
@@ -48,7 +51,9 @@ class UnifiedAIService {
48
51
  endpoint = '/api/generate';
49
52
  }
50
53
  try {
51
- const response = await fetch(`${this.config.baseUrl}${endpoint}`, {
54
+ const fullUrl = `${this.config.baseUrl}${endpoint}`;
55
+ console.log('AI Config - full URL:', fullUrl);
56
+ const response = await fetch(fullUrl, {
52
57
  method: 'POST',
53
58
  headers,
54
59
  body: JSON.stringify(requestBody),
@@ -77,73 +82,197 @@ class UnifiedAIService {
77
82
  return data.response || '';
78
83
  }
79
84
  }
80
- buildUserProfileContext(context) {
85
+ translateHealthGoals(goals, language) {
86
+ if (language === 'bs') {
87
+ const translations = {
88
+ 'weight_loss': 'gubljenje težine',
89
+ 'muscle_gain': 'gradnja mišića',
90
+ 'healthy_eating': 'zdrava ishrana',
91
+ 'heart_health': 'zdravlje srca',
92
+ 'diabetes_friendly': 'pogodno za dijabetičare',
93
+ 'energy_boost': 'povećanje energije',
94
+ 'anti_inflammatory': 'antiupalno',
95
+ 'digestive_health': 'zdravlje probave',
96
+ 'bone_health': 'zdravlje kostiju',
97
+ 'immune_support': 'podrška imunitetu'
98
+ };
99
+ return goals.map(goal => translations[goal] || goal);
100
+ }
101
+ return goals;
102
+ }
103
+ translateActivityLevel(level, language) {
104
+ if (language === 'bs') {
105
+ const translations = {
106
+ 'sedentary': 'sjedilački način života',
107
+ 'lightly_active': 'lagano aktivan',
108
+ 'moderately_active': 'umjereno aktivan',
109
+ 'very_active': 'vrlo aktivan',
110
+ 'extremely_active': 'izuzetno aktivan'
111
+ };
112
+ return translations[level] || level;
113
+ }
114
+ return level;
115
+ }
116
+ translateDietaryRestrictions(restrictions, language) {
117
+ if (language === 'bs') {
118
+ const translations = {
119
+ 'vegetarian': 'vegetarijanska ishrana',
120
+ 'vegan': 'veganska ishrana',
121
+ 'gluten_free': 'bez glutena',
122
+ 'dairy_free': 'bez mliječnih proizvoda',
123
+ 'low_carb': 'niska količina ugljiko hidrata',
124
+ 'low_fat': 'mala količina masti',
125
+ 'low_sodium': 'mala količina soli',
126
+ 'keto': 'ketogena ishrana',
127
+ 'paleo': 'paleo ishrana',
128
+ 'mediterranean': 'mediteranska ishrana'
129
+ };
130
+ return restrictions.map(restriction => translations[restriction] || restriction);
131
+ }
132
+ return restrictions;
133
+ }
134
+ buildUserProfileContext(context, language = 'en') {
81
135
  const { userProfile, preferences, nutritionalTargets } = context;
136
+ const isBosnian = language === 'bs';
82
137
  let profileText = '';
83
- if (userProfile.age)
84
- profileText += `Age: ${userProfile.age} years old. `;
85
- if (userProfile.gender)
86
- profileText += `Gender: ${userProfile.gender}. `;
138
+ if (userProfile.age) {
139
+ profileText += isBosnian
140
+ ? `Godine: ${userProfile.age}. `
141
+ : `Age: ${userProfile.age} years old. `;
142
+ }
143
+ if (userProfile.gender) {
144
+ const genderText = isBosnian
145
+ ? (userProfile.gender === 'male' ? 'muškarac' : 'žena')
146
+ : userProfile.gender;
147
+ profileText += isBosnian
148
+ ? `Pol: ${genderText}. `
149
+ : `Gender: ${genderText}. `;
150
+ }
87
151
  if (userProfile.height && userProfile.weight) {
88
- profileText += `Height: ${userProfile.height}cm, Weight: ${userProfile.weight}kg. `;
152
+ profileText += isBosnian
153
+ ? `Visina: ${userProfile.height}cm, Težina: ${userProfile.weight}kg. `
154
+ : `Height: ${userProfile.height}cm, Weight: ${userProfile.weight}kg. `;
155
+ }
156
+ if (userProfile.activityLevel) {
157
+ const activityText = this.translateActivityLevel(userProfile.activityLevel, language);
158
+ profileText += isBosnian
159
+ ? `Nivo aktivnosti: ${activityText}. `
160
+ : `Activity level: ${activityText}. `;
89
161
  }
90
- if (userProfile.activityLevel)
91
- profileText += `Activity level: ${userProfile.activityLevel}. `;
92
162
  if (userProfile.healthGoals?.length) {
93
- profileText += `Health goals: ${userProfile.healthGoals.join(', ')}. `;
163
+ const goalsText = this.translateHealthGoals(userProfile.healthGoals, language).join(', ');
164
+ profileText += isBosnian
165
+ ? `Zdravstveni ciljevi: ${goalsText}. `
166
+ : `Health goals: ${goalsText}. `;
94
167
  }
95
168
  if (userProfile.dietaryRestrictions?.length) {
96
- profileText += `Dietary restrictions: ${userProfile.dietaryRestrictions.join(', ')}. `;
169
+ const restrictionsText = this.translateDietaryRestrictions(userProfile.dietaryRestrictions, language).join(', ');
170
+ profileText += isBosnian
171
+ ? `Dijetska ograničenja: ${restrictionsText}. `
172
+ : `Dietary restrictions: ${restrictionsText}. `;
97
173
  }
98
174
  if (userProfile.allergies?.length) {
99
- profileText += `Allergies: ${userProfile.allergies.join(', ')}. `;
175
+ profileText += isBosnian
176
+ ? `Alergije: ${userProfile.allergies.join(', ')}. `
177
+ : `Allergies: ${userProfile.allergies.join(', ')}. `;
100
178
  }
101
179
  if (userProfile.healthConditions?.length) {
102
- profileText += `Health conditions: ${userProfile.healthConditions.join(', ')}. `;
180
+ profileText += isBosnian
181
+ ? `Zdravstvena stanja: ${userProfile.healthConditions.join(', ')}. `
182
+ : `Health conditions: ${userProfile.healthConditions.join(', ')}. `;
103
183
  }
104
184
  if (preferences?.cuisineTypes?.length) {
105
- profileText += `Preferred cuisines: ${preferences.cuisineTypes.join(', ')}. `;
185
+ profileText += isBosnian
186
+ ? `Preferirane kuhinje: ${preferences.cuisineTypes.join(', ')}. `
187
+ : `Preferred cuisines: ${preferences.cuisineTypes.join(', ')}. `;
106
188
  }
107
189
  if (preferences?.cookingTime) {
108
- profileText += `Preferred cooking time: ${preferences.cookingTime} minutes max. `;
190
+ profileText += isBosnian
191
+ ? `Željeno vrijeme kuhanja: ${preferences.cookingTime} minuta maksimalno. `
192
+ : `Preferred cooking time: ${preferences.cookingTime} minutes max. `;
109
193
  }
110
194
  if (nutritionalTargets) {
111
- profileText += `Nutritional targets: `;
195
+ profileText += isBosnian
196
+ ? `Nutritivni ciljevi: `
197
+ : `Nutritional targets: `;
112
198
  if (nutritionalTargets.calories)
113
- profileText += `${nutritionalTargets.calories} calories, `;
199
+ profileText += isBosnian
200
+ ? `${nutritionalTargets.calories} kalorija, `
201
+ : `${nutritionalTargets.calories} calories, `;
114
202
  if (nutritionalTargets.protein)
115
- profileText += `${nutritionalTargets.protein}g protein, `;
203
+ profileText += isBosnian
204
+ ? `${nutritionalTargets.protein}g proteina, `
205
+ : `${nutritionalTargets.protein}g protein, `;
116
206
  if (nutritionalTargets.carbs)
117
- profileText += `${nutritionalTargets.carbs}g carbs, `;
207
+ profileText += isBosnian
208
+ ? `${nutritionalTargets.carbs}g ugljiko hidrata, `
209
+ : `${nutritionalTargets.carbs}g carbs, `;
118
210
  if (nutritionalTargets.fat)
119
- profileText += `${nutritionalTargets.fat}g fat. `;
211
+ profileText += isBosnian
212
+ ? `${nutritionalTargets.fat}g masti. `
213
+ : `${nutritionalTargets.fat}g fat. `;
120
214
  }
121
215
  return profileText.trim();
122
216
  }
123
217
  parseJSONResponse(response) {
124
218
  try {
125
219
  console.log('Raw AI response for debugging:', response);
126
- // Clean up the response - remove any markdown code blocks and extra text
127
220
  let cleanedResponse = response.trim();
128
- // Remove markdown code blocks more aggressively
129
221
  cleanedResponse = cleanedResponse.replace(/```json\s*/gi, '');
130
222
  cleanedResponse = cleanedResponse.replace(/```\s*$/gi, '');
131
- cleanedResponse = cleanedResponse.replace(/```\s*$/gi, '');
132
- // Remove any explanatory text before/after JSON
133
- const jsonStartIndex = cleanedResponse.indexOf('{');
134
- const jsonEndIndex = cleanedResponse.lastIndexOf('}');
135
- if (jsonStartIndex !== -1 && jsonEndIndex !== -1 && jsonEndIndex > jsonStartIndex) {
136
- cleanedResponse = cleanedResponse.substring(jsonStartIndex, jsonEndIndex + 1);
223
+ const jsonObjects = [];
224
+ let startIndex = 0;
225
+ while (startIndex < cleanedResponse.length) {
226
+ const jsonStart = cleanedResponse.indexOf('{', startIndex);
227
+ if (jsonStart === -1)
228
+ break;
229
+ let braceCount = 0;
230
+ let jsonEnd = -1;
231
+ for (let i = jsonStart; i < cleanedResponse.length; i++) {
232
+ if (cleanedResponse[i] === '{')
233
+ braceCount++;
234
+ if (cleanedResponse[i] === '}')
235
+ braceCount--;
236
+ if (braceCount === 0) {
237
+ jsonEnd = i;
238
+ break;
239
+ }
240
+ }
241
+ if (jsonEnd !== -1) {
242
+ const jsonObject = cleanedResponse.substring(jsonStart, jsonEnd + 1);
243
+ jsonObjects.push(jsonObject);
244
+ startIndex = jsonEnd + 1;
245
+ }
246
+ else {
247
+ break;
248
+ }
249
+ }
250
+ // If we found multiple JSON objects, merge them
251
+ if (jsonObjects.length > 1) {
252
+ try {
253
+ const mergedObject = jsonObjects.reduce((acc, jsonStr) => {
254
+ const parsed = JSON.parse(jsonStr);
255
+ return { ...acc, ...parsed };
256
+ }, {});
257
+ cleanedResponse = JSON.stringify(mergedObject);
258
+ }
259
+ catch (e) {
260
+ // If merging fails, use the first JSON object
261
+ cleanedResponse = jsonObjects[0];
262
+ }
263
+ }
264
+ else if (jsonObjects.length === 1) {
265
+ cleanedResponse = jsonObjects[0];
137
266
  }
138
267
  console.log('Cleaned JSON response:', cleanedResponse);
139
- // Try parsing the cleaned response
268
+ // Try parsing the cleaned response first
140
269
  try {
141
270
  const parsed = JSON.parse(cleanedResponse);
142
271
  return parsed;
143
272
  }
144
273
  catch (parseError) {
145
274
  console.log('Direct parse failed, trying to fix common issues:', parseError);
146
- // If direct parsing fails, try to fix common JSON issues
275
+ // If direct parsing fails, try to fix common JSON issues more carefully
147
276
  const fixedJson = this.fixCommonJSONIssues(cleanedResponse);
148
277
  console.log('Fixed JSON:', fixedJson);
149
278
  return JSON.parse(fixedJson);
@@ -158,79 +287,173 @@ class UnifiedAIService {
158
287
  }
159
288
  fixCommonJSONIssues(jsonString) {
160
289
  let fixed = jsonString;
161
- // Fix common issues with AI-generated JSON
162
- // 1. Fix trailing commas in arrays and objects
290
+ // Fix only the most common issues with AI-generated JSON
291
+ // 1. Fix trailing commas in arrays and objects (most common issue)
163
292
  fixed = fixed.replace(/,\s*([}\]])/g, '$1');
164
- // 2. Fix unquoted property names
165
- fixed = fixed.replace(/([{\s,])([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":');
166
- // 3. Fix single quotes instead of double quotes
167
- fixed = fixed.replace(/'/g, '"');
168
- // 4. Fix missing quotes around string values
169
- fixed = fixed.replace(/:\s*([a-zA-Z_][a-zA-Z0-9_]*)([\s,}])/g, ': "$1"$2');
170
- // 5. Fix line breaks in strings
171
- fixed = fixed.replace(/"([^"]*)\n([^"]*)"/g, '"$1\\n$2"');
172
- // 6. Fix escaped quotes that might be double-escaped
173
- fixed = fixed.replace(/\\\\"/g, '\\"');
293
+ // 2. Fix single quotes instead of double quotes (only if they appear to be string delimiters)
294
+ fixed = fixed.replace(/'([^']*)'/g, '"$1"');
295
+ // 3. Fix malformed quantities like "100g" -> 100 in JSON values (but keep unit separate)
296
+ fixed = fixed.replace(/"quantity":\s*"(\d+)g"/g, '"quantity": $1');
297
+ fixed = fixed.replace(/"quantity":\s*"(\d+\.?\d*)g"/g, '"quantity": $1');
298
+ fixed = fixed.replace(/"quantity":\s*"(\d+)mg"/g, '"quantity": $1');
299
+ fixed = fixed.replace(/"quantity":\s*"(\d+\.?\d*)mg"/g, '"quantity": $1');
300
+ fixed = fixed.replace(/"quantity":\s*"(\d+)"/g, '"quantity": $1');
301
+ fixed = fixed.replace(/"quantity":\s*"(\d+\.?\d*)"/g, '"quantity": $1');
302
+ // 4. Fix missing commas in arrays (common in AI-generated JSON)
303
+ fixed = fixed.replace(/"\s*\n\s*"/g, '",\n"');
304
+ fixed = fixed.replace(/}\s*\n\s*{/g, '},\n{');
305
+ fixed = fixed.replace(/]\s*\n\s*\[/g, '],\n[');
174
306
  return fixed;
175
307
  }
176
- async generateMealPlan(request) {
177
- const systemPrompt = `You are a professional nutritionist and meal planning expert. Generate personalized meal plans based on user profiles, dietary restrictions, and health goals.
178
-
179
- CRITICAL INSTRUCTIONS:
180
- 1. Respond with ONLY valid JSON - no explanations, no markdown, no extra text
181
- 2. Your ENTIRE response must be a single JSON object starting with { and ending with }
182
- 3. Do NOT use triple backticks json code blocks
183
- 4. Do NOT add any text before or after the JSON
184
- 5. Ensure all strings are properly double-quoted
185
- 6. Numbers must NOT be quoted
186
- 7. No trailing commas allowed
187
-
188
- The JSON response must follow this exact structure:
189
- {
190
- "name": "string",
191
- "description": "string",
192
- "type": "daily" | "weekly",
193
- "meals": [
194
- {
195
- "name": "string",
196
- "category": "breakfast" | "lunch" | "dinner" | "snack",
197
- "description": "string",
198
- "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
199
- "instructions": ["string"],
200
- "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
201
- "prepTime": number,
202
- "cookTime": number,
203
- "servings": number,
204
- "difficulty": "easy" | "medium" | "hard",
205
- "tags": ["string"],
206
- "tips": ["string"]
207
- }
208
- ],
209
- "totalNutrition": {"calories": number, "protein": number, "carbs": number, "fat": number},
210
- "recommendations": ["string"],
211
- "shoppingList": [{"ingredient": "string", "quantity": number, "unit": "string", "category": "string"}]
212
- }
213
-
308
+ async generateMealPlan(request, language = 'en') {
309
+ const isBosnian = language === 'bs';
310
+ const systemPrompt = isBosnian
311
+ ? `Ti si profesionalni nutricionista i ekspert za planiranje obroka sa dubokim poznavanjem bosanske kuhinje. Generiši personalizovane planove obroka zasnovane na profilima korisnika, dijetetskim ograničenjima i zdravstvenim ciljevima.
312
+
313
+ VAŽNA PRAVILA ZA BOSANSKI JEZIK:
314
+ 1. Koristi ispravnu bosansku terminologiju:
315
+ - "smoothie" ne "smoothij"
316
+ - "fileti" ne "fillet"
317
+ - "namirnice" ili "sastojci" ne "ingredients"
318
+ 2. Bilo specifičan kod namirnica:
319
+ - Umjesto "riblji fileti" navedi vrstu: "losos fileti", "tuna fileti", "pastrmka fileti", "bakalar fileti"
320
+ - Umjesto "zeleno meso" navedi vrstu: "piletina prsa", "govedina", "puretina"
321
+ - Umjesto "povrće" navedi konkretne vrste: "krompir", "mrkva", "paprika", "krastavac"
322
+ 3. Koristi ispravnu bosansku gramatiku:
323
+ - "Umjesto ribljih fileta" ne "Umjesto riblje filuta"
324
+ - "Kuhajte filete" ne "Kuvajte filut"
325
+ - "Pripremite" ne "Izvršite"
326
+ 4. KULINARSKI VERBI I TERMINOLOGIJA:
327
+ - "Pržite" za frying, "pecite" za baking, "Kuhajte" za boiling
328
+ - "kuhano" ne "kuvano" (past participle)
329
+ - "fileti" ne "filet" (plural form)
330
+ - "cimet" ne "cimeta" (nominative case)
331
+ - "sol" ne "sol" (correct), "šećer" ne "šećera" (nominative)
332
+ 5. NAZIVI JELA:
333
+ - Koristi konkretne nazive: "Pečeni losos sa špinatom" ne "Riblji filet sa špinatom"
334
+ - Specifikujte vrstu ribe u nazivu jela
335
+ - Koristi ispravne gramatičke krajeve
336
+
337
+ KRITIČNA UPUTSTVA:
338
+ 1. Odgovori SAMO sa važećim JSON - bez objašnjenja, bez markdown, bez dodatnog teksta
339
+ 2. Cijeli tvoj odgovor mora biti jedan JSON objekat koji počinje sa { i završava sa }
340
+ 3. NE koristi triple backticks json code blokove
341
+ 4. NE dodaj nikakav tekst pre ili posle JSON-a
342
+ 5. Osiguraj da su svi stringovi pravilno u dvostrukim navodnicima
343
+ 6. Brojevi NE smiju biti u navodnicima
344
+ 7. Nema dozvoljenih zareza na kraju
345
+
346
+ JSON odgovor mora da prati ovu tačnu strukturu:
347
+ {
348
+ "name": "string",
349
+ "description": "string",
350
+ "type": "daily" | "weekly",
351
+ "meals": [
352
+ {
353
+ "name": "string",
354
+ "category": "breakfast" | "lunch" | "dinner" | "snack",
355
+ "description": "string",
356
+ "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
357
+ "instructions": ["string"],
358
+ "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
359
+ "prepTime": number,
360
+ "cookTime": number,
361
+ "servings": number,
362
+ "difficulty": "easy" | "medium" | "hard",
363
+ "tags": ["string"],
364
+ "tips": ["string"]
365
+ }
366
+ ],
367
+ "totalNutrition": {"calories": number, "protein": number, "carbs": number, "fat": number},
368
+ "recommendations": ["string"],
369
+ "shoppingList": [{"ingredient": "string", "quantity": number, "unit": "string", "category": "string"}]
370
+ // VAŽNO: quantity mora biti broj (npr: 100, ne "100g"), unit sadrži jedinicu (npr: "g", "kom", "ml")
371
+ }
372
+
373
+ KRŠENJE OVIH UPUTSTVA ĆE DOVESTI DO NEVAŽEĆEG IZLAZA.`
374
+ : `You are a professional nutritionist and meal planning expert. Generate personalized meal plans based on user profiles, dietary restrictions, and health goals.
375
+
376
+ CRITICAL INSTRUCTIONS:
377
+ 1. Respond with ONLY valid JSON - no explanations, no markdown, no extra text
378
+ 2. Your ENTIRE response must be a single JSON object starting with { and ending with }
379
+ 3. Do NOT use triple backticks json code blocks
380
+ 4. Do NOT add any text before or after the JSON
381
+ 5. Ensure all strings are properly double-quoted
382
+ 6. Numbers must NOT be quoted
383
+ 7. No trailing commas allowed
384
+
385
+ The JSON response must follow this exact structure:
386
+ {
387
+ "name": "string",
388
+ "description": "string",
389
+ "type": "daily" | "weekly",
390
+ "meals": [
391
+ {
392
+ "name": "string",
393
+ "category": "breakfast" | "lunch" | "dinner" | "snack",
394
+ "description": "string",
395
+ "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
396
+ "instructions": ["string"],
397
+ "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
398
+ "prepTime": number,
399
+ "cookTime": number,
400
+ "servings": number,
401
+ "difficulty": "easy" | "medium" | "hard",
402
+ "tags": ["string"],
403
+ "tips": ["string"]
404
+ }
405
+ ],
406
+ "totalNutrition": {"calories": number, "protein": number, "carbs": number, "fat": number},
407
+ "recommendations": ["string"],
408
+ "shoppingList": [{"ingredient": "string", "quantity": number, "unit": "string", "category": "string"}]
409
+ // IMPORTANT: quantity must be a number (e.g: 100, not "100g"), unit contains the measurement (e.g: "g", "pcs", "ml")
410
+ }
411
+
214
412
  VIOLATION OF THESE INSTRUCTIONS WILL RESULT IN INVALID OUTPUT.`;
215
- const userContext = this.buildUserProfileContext(request.context);
216
- const planType = request.type === 'daily' ? 'one day' : 'seven days';
217
- let prompt = `Create a ${planType} meal plan for a person with the following profile:\n${userContext}\n\n`;
413
+ const userContext = this.buildUserProfileContext(request.context, language);
414
+ const planTypeText = isBosnian
415
+ ? (request.type === 'daily' ? 'jedan dan' : 'sedam dana')
416
+ : (request.type === 'daily' ? 'one day' : 'seven days');
417
+ let prompt = isBosnian
418
+ ? `Kreiraj plan obroka za ${planTypeText} za osobu sa sljedećim profilom:\n${userContext}\n\n`
419
+ : `Create a ${planTypeText} meal plan for a person with the following profile:\n${userContext}\n\n`;
218
420
  if (request.existingMeals?.length) {
219
- prompt += `Avoid repeating these meals: ${request.existingMeals.join(', ')}\n\n`;
421
+ prompt += isBosnian
422
+ ? `Izbjegni ponavljanje ovih obroka: ${request.existingMeals.join(', ')}\n\n`
423
+ : `Avoid repeating these meals: ${request.existingMeals.join(', ')}\n\n`;
220
424
  }
221
425
  if (request.avoidIngredients?.length) {
222
- prompt += `Avoid these ingredients: ${request.avoidIngredients.join(', ')}\n\n`;
426
+ prompt += isBosnian
427
+ ? `Izbjegni ove sastojke: ${request.avoidIngredients.join(', ')}\n\n`
428
+ : `Avoid these ingredients: ${request.avoidIngredients.join(', ')}\n\n`;
223
429
  }
224
430
  if (request.focusAreas?.length) {
225
- prompt += `Focus on these areas: ${request.focusAreas.join(', ')}\n\n`;
431
+ prompt += isBosnian
432
+ ? `Fokusiraj se na ove oblasti: ${request.focusAreas.join(', ')}\n\n`
433
+ : `Focus on these areas: ${request.focusAreas.join(', ')}\n\n`;
226
434
  }
227
435
  if (request.type === 'daily') {
228
- prompt += `Generate 4 meals: breakfast, lunch, dinner, and one snack. `;
436
+ prompt += isBosnian
437
+ ? `Generiši 4 obroka: doručak, ručak, večera i jednu grickalicu. `
438
+ : `Generate 4 meals: breakfast, lunch, dinner, and one snack. `;
229
439
  }
230
440
  else {
231
- prompt += `Generate 28 meals total: 4 meals per day for 7 days (breakfast, lunch, dinner, snack each day). `;
232
- }
233
- prompt += `Ensure nutritional balance, variety, and alignment with the user's goals. Include detailed cooking instructions, accurate nutritional information, and practical tips.`;
441
+ prompt += isBosnian
442
+ ? `Generiši ukupno 28 obroka: 4 obroka dnevno za 7 dana (doručak, ručak, večera, grickalica svaki dan). `
443
+ : `Generate 28 meals total: 4 meals per day for 7 days (breakfast, lunch, dinner, snack each day). `;
444
+ }
445
+ prompt += isBosnian
446
+ ? `Osiguraj nutricionsku ravnotežu, raznovrsnost i usklađenost sa ciljevima korisnika. Uključi detaljna uputstva za kuvanje, tačne nutricione informacije i praktične savjete.
447
+
448
+ VAŽNO: Koristi specifične bosanske nazive za namirnice i jela. Sva imena obroka, namirnica, uputstva i preporuke moraju biti na ispravnom bosanskom jeziku.
449
+ - Koristi "fileti" umjesto "fillet"
450
+ - Koristi "namirnice" ili "sastojci" umjesto "ingredients"
451
+ - Navedi konkretne vrste mesa: "piletina prsa", "govedina", "puretina"
452
+ - Navedi konkretne vrste ribe: "losos fileti", "tuna fileti", "pastrmka fileti", "bakalar fileti"
453
+ - Koristi ispravne kulinarske verbe: "Pržite" (fry), "pecite" (bake), "Kuhajte" (boil)
454
+ - Koristi ispravnu bosansku gramatiku: "kuhano" ne "kuvano", "fileti" ne "filet"
455
+ - Nazivi jela moraju biti specifični: "Pečeni losos sa špinatom" ne "Riblji filet sa špinatom"`
456
+ : `Ensure nutritional balance, variety, and alignment with the user's goals. Include detailed cooking instructions, accurate nutritional information, and practical tips.`;
234
457
  try {
235
458
  const response = await this.makeRequest(prompt, systemPrompt);
236
459
  // Extract the actual response content
@@ -260,50 +483,117 @@ VIOLATION OF THESE INSTRUCTIONS WILL RESULT IN INVALID OUTPUT.`;
260
483
  };
261
484
  }
262
485
  }
263
- async generateRecipe(request) {
264
- const systemPrompt = `You are a professional chef and nutritionist. Generate detailed, healthy recipes based on user requirements.
265
-
266
- CRITICAL INSTRUCTIONS:
267
- 1. Respond with ONLY valid JSON - no explanations, no markdown, no extra text
268
- 2. Your ENTIRE response must be a single JSON object starting with { and ending with }
269
- 3. Do NOT use triple backticks json code blocks
270
- 4. Do NOT add any text before or after the JSON
271
- 5. Ensure all strings are properly double-quoted
272
- 6. Numbers must NOT be quoted
273
- 7. No trailing commas allowed
274
-
275
- The JSON response must follow this exact structure:
276
- {
277
- "name": "string",
278
- "category": "breakfast" | "lunch" | "dinner" | "snack",
279
- "description": "string",
280
- "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
281
- "instructions": ["string"],
282
- "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
283
- "prepTime": number,
284
- "cookTime": number,
285
- "servings": number,
286
- "difficulty": "easy" | "medium" | "hard",
287
- "tags": ["string"],
288
- "tips": ["string"]
289
- }
290
-
486
+ async generateRecipe(request, language = 'en') {
487
+ const isBosnian = language === 'bs';
488
+ const systemPrompt = isBosnian
489
+ ? `Ti si profesionalni kuvar i nutricionista sa dubokim poznavanjem bosanske kuhinje. Generiši detaljne, zdrave recepte zasnovane na zahtjevima korisnika.
490
+
491
+ VAŽNA PRAVILA ZA BOSANSKI JEZIK:
492
+ 1. Koristi ispravnu bosansku terminologiju:
493
+ - "smoothie" ne "smoothij"
494
+ - "fileti" ne "fillet"
495
+ - "namirnice" ili "sastojci" ne "ingredients"
496
+ 2. Bilo specifičan kod namirnica:
497
+ - Umjesto "riblji fileti" navedi vrstu: "losos fileti", "tuna fileti", "pastrmka fileti", "bakalar fileti"
498
+ - Umjesto "zeleno meso" navedi vrstu: "piletina prsa", "govedina", "puretina"
499
+ - Umjesto "povrće" navedi konkretne vrste: "krompir", "mrkva", "paprika", "krastavac"
500
+ 3. Koristi ispravnu bosansku gramatiku u uputstvima:
501
+ - "Pržite" za frying, "pecite" za baking, "Kuhajte" za boiling
502
+ - "kuhano" ne "kuvano" (past participle)
503
+ - "fileti" ne "filet" (plural form)
504
+ - "cimet" ne "cimeta" (correct grammar)
505
+ 4. NAZIVI JELA:
506
+ - Koristi konkretne nazive: "Pečeni losos sa špinatom" ne "Riblji filet sa špinatom"
507
+ - Specifikujte vrstu ribe u nazivu jela
508
+
509
+ KRITIČNA UPUTSTVA:
510
+ 1. Odgovori SAMO sa važećim JSON - bez objašnjenja, bez markdown, bez dodatnog teksta
511
+ 2. CEO tvoj odgovor mora biti jedan JSON objekat koji počinje sa { i završava sa }
512
+ 3. NE koristi triple backticks json code blokove
513
+ 4. NE dodaj nikakav tekst pre ili posle JSON-a
514
+ 5. Osiguraj da su svi stringovi pravilno u dvostrukim navodnicima
515
+ 6. Brojevi NE smaju biti u navodnicima
516
+ 7. Nema dozvoljenih zareza na kraju
517
+
518
+ JSON odgovor mora da prati ovu tačnu strukturu:
519
+ {
520
+ "name": "string",
521
+ "category": "breakfast" | "lunch" | "dinner" | "snack",
522
+ "description": "string",
523
+ "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
524
+ "instructions": ["string"],
525
+ "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
526
+ "prepTime": number,
527
+ "cookTime": number,
528
+ "servings": number,
529
+ "difficulty": "easy" | "medium" | "hard",
530
+ "tags": ["string"],
531
+ "tips": ["string"]
532
+ }
533
+
534
+ KRŠENJE OVIH UPUTSTVA ĆE DOVESTI DO NEVAŽEĆEG IZLAZA.`
535
+ : `You are a professional chef and nutritionist. Generate detailed, healthy recipes based on user requirements.
536
+
537
+ CRITICAL INSTRUCTIONS:
538
+ 1. Respond with ONLY valid JSON - no explanations, no markdown, no extra text
539
+ 2. Your ENTIRE response must be a single JSON object starting with { and ending with }
540
+ 3. Do NOT use triple backticks json code blocks
541
+ 4. Do NOT add any text before or after the JSON
542
+ 5. Ensure all strings are properly double-quoted
543
+ 6. Numbers must NOT be quoted
544
+ 7. No trailing commas allowed
545
+
546
+ The JSON response must follow this exact structure:
547
+ {
548
+ "name": "string",
549
+ "category": "breakfast" | "lunch" | "dinner" | "snack",
550
+ "description": "string",
551
+ "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
552
+ "instructions": ["string"],
553
+ "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
554
+ "prepTime": number,
555
+ "cookTime": number,
556
+ "servings": number,
557
+ "difficulty": "easy" | "medium" | "hard",
558
+ "tags": ["string"],
559
+ "tips": ["string"]
560
+ }
561
+
291
562
  VIOLATION OF THESE INSTRUCTIONS WILL RESULT IN INVALID OUTPUT.`;
292
- const userContext = this.buildUserProfileContext(request.context);
293
- let prompt = `Create a ${request.mealType} recipe for a person with the following profile:\n${userContext}\n\n`;
563
+ const userContext = this.buildUserProfileContext(request.context, language);
564
+ let prompt = isBosnian
565
+ ? `Kreiraj recept za ${request.mealType} za osobu sa sljedećim profilom:\n${userContext}\n\n`
566
+ : `Create a ${request.mealType} recipe for a person with the following profile:\n${userContext}\n\n`;
294
567
  if (request.ingredients?.length) {
295
- prompt += `Include these ingredients: ${request.ingredients.join(', ')}\n`;
568
+ prompt += isBosnian
569
+ ? `Uključi ove sastojke: ${request.ingredients.join(', ')}\n`
570
+ : `Include these ingredients: ${request.ingredients.join(', ')}\n`;
296
571
  }
297
572
  if (request.cookingTime) {
298
- prompt += `Maximum cooking time: ${request.cookingTime} minutes\n`;
573
+ prompt += isBosnian
574
+ ? `Maksimalno vrijeme kuhanja: ${request.cookingTime} minuta\n`
575
+ : `Maximum cooking time: ${request.cookingTime} minutes\n`;
299
576
  }
300
577
  if (request.difficulty) {
301
- prompt += `Difficulty level: ${request.difficulty}\n`;
578
+ prompt += isBosnian
579
+ ? `Nivo težine: ${request.difficulty}\n`
580
+ : `Difficulty level: ${request.difficulty}\n`;
302
581
  }
303
582
  if (request.servings) {
304
- prompt += `Number of servings: ${request.servings}\n`;
305
- }
306
- prompt += `\nGenerate a detailed recipe with accurate nutritional information, clear instructions, and helpful cooking tips.`;
583
+ prompt += isBosnian
584
+ ? `Broj porcija: ${request.servings}\n`
585
+ : `Number of servings: ${request.servings}\n`;
586
+ }
587
+ prompt += isBosnian
588
+ ? `\nGeneriši detaljan recept sa tačnim nutricionim informacijama, jasnim uputstvima i korisnim savjetima za kuvanje.
589
+
590
+ VAŽNO: Koristi specifične bosanske nazive za namirnice. Sva imena recepata, namirnica, uputstva i savjeti moraju biti na ispravnom bosanskom jeziku.
591
+ - Koristi "fileti" umjesto "fillet"
592
+ - Navedi konkretne vrste mesa i ribe: "piletina prsa", "losos fileti", "tuna fileti", "pastrmka fileti", "bakalar fileti"
593
+ - Koristi ispravne kulinarske verbe: "Pržite" (fry), "pecite" (bake), "Kuhajte" (boil), "Miješajte" (stir)
594
+ - Koristi ispravnu bosansku gramatiku: "kuhano" ne "kuvano", "fileti" ne "filet", "cimet" ne "cimeta"
595
+ - Nazivi recepata moraju biti specifični: "Pečeni losos sa špinatom" ne "Riblji filet sa špinatom"`
596
+ : `\nGenerate a detailed recipe with accurate nutritional information, clear instructions, and helpful cooking tips.`;
307
597
  try {
308
598
  const response = await this.makeRequest(prompt, systemPrompt);
309
599
  // Extract the actual response content
@@ -334,40 +624,40 @@ VIOLATION OF THESE INSTRUCTIONS WILL RESULT IN INVALID OUTPUT.`;
334
624
  }
335
625
  }
336
626
  async suggestMealVariations(meal, context) {
337
- const systemPrompt = `You are a creative chef. Generate 3-5 variations of the given meal while maintaining similar nutritional profile. Always respond with valid JSON array format only.
338
-
339
- The JSON response must be an array of meal objects following this structure:
340
- [
341
- {
342
- "name": "string",
343
- "category": "breakfast" | "lunch" | "dinner" | "snack",
344
- "description": "string",
345
- "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
346
- "instructions": ["string"],
347
- "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
348
- "prepTime": number,
349
- "cookTime": number,
350
- "servings": number,
351
- "difficulty": "easy" | "medium" | "hard",
352
- "tags": ["string"],
353
- "tips": ["string"]
354
- }
627
+ const systemPrompt = `You are a creative chef. Generate 3-5 variations of the given meal while maintaining similar nutritional profile. Always respond with valid JSON array format only.
628
+
629
+ The JSON response must be an array of meal objects following this structure:
630
+ [
631
+ {
632
+ "name": "string",
633
+ "category": "breakfast" | "lunch" | "dinner" | "snack",
634
+ "description": "string",
635
+ "ingredients": [{"name": "string", "quantity": number, "unit": "string"}],
636
+ "instructions": ["string"],
637
+ "nutrition": {"calories": number, "protein": number, "carbs": number, "fat": number, "fiber": number, "sugar": number, "sodium": number},
638
+ "prepTime": number,
639
+ "cookTime": number,
640
+ "servings": number,
641
+ "difficulty": "easy" | "medium" | "hard",
642
+ "tags": ["string"],
643
+ "tips": ["string"]
644
+ }
355
645
  ]`;
356
- const userContext = this.buildUserProfileContext(context);
357
- const prompt = `Create 3-5 variations of this meal: "${meal.name}"
358
-
359
- Original meal details:
360
- - Category: ${meal.category}
361
- - Calories: ${meal.nutrition.calories}
362
- - Protein: ${meal.nutrition.protein}g
363
- - Main ingredients: ${meal.ingredients.slice(0, 5).map(i => i.name).join(', ')}
364
-
365
- User profile: ${userContext}
366
-
367
- Generate variations that:
368
- 1. Keep similar nutritional values (±10% calories)
369
- 2. Use different cooking methods or ingredient substitutions
370
- 3. Maintain the same meal category
646
+ const userContext = this.buildUserProfileContext(context, 'en'); // Default to English for variations
647
+ const prompt = `Create 3-5 variations of this meal: "${meal.name}"
648
+
649
+ Original meal details:
650
+ - Category: ${meal.category}
651
+ - Calories: ${meal.nutrition.calories}
652
+ - Protein: ${meal.nutrition.protein}g
653
+ - Main ingredients: ${meal.ingredients.slice(0, 5).map(i => i.name).join(', ')}
654
+
655
+ User profile: ${userContext}
656
+
657
+ Generate variations that:
658
+ 1. Keep similar nutritional values (±10% calories)
659
+ 2. Use different cooking methods or ingredient substitutions
660
+ 3. Maintain the same meal category
371
661
  4. Consider the user's dietary restrictions and preferences`;
372
662
  try {
373
663
  const response = await this.makeRequest(prompt, systemPrompt);
@@ -390,38 +680,66 @@ Generate variations that:
390
680
  };
391
681
  }
392
682
  }
393
- async analyzeMealPlan(meals, context) {
394
- const systemPrompt = `You are a registered dietitian. Analyze meal plans and provide professional nutritional advice. Always respond with valid JSON format only.
395
-
396
- The JSON response must follow this exact structure:
397
- {
398
- "nutritionalAnalysis": "string",
399
- "recommendations": ["string"],
400
- "improvements": ["string"]
683
+ async analyzeMealPlan(meals, context, language = 'en') {
684
+ const isBosnian = language === 'bs';
685
+ const systemPrompt = isBosnian
686
+ ? `Ti si registrovani nutricionista. Analiziraj planove obroka i pruži profesionalne nutricione savjete. Uvek odgovaraj sa važećim JSON formatom samo.
687
+
688
+ JSON odgovor mora da prati ovu tačnu strukturu:
689
+ {
690
+ "nutritionalAnalysis": "string",
691
+ "recommendations": ["string"],
692
+ "improvements": ["string"]
693
+ }`
694
+ : `You are a registered dietitian. Analyze meal plans and provide professional nutritional advice. Always respond with valid JSON format only.
695
+
696
+ The JSON response must follow this exact structure:
697
+ {
698
+ "nutritionalAnalysis": "string",
699
+ "recommendations": ["string"],
700
+ "improvements": ["string"]
401
701
  }`;
402
- const userContext = this.buildUserProfileContext(context);
702
+ const userContext = this.buildUserProfileContext(context, language);
403
703
  const totalNutrition = meals.reduce((total, meal) => ({
404
704
  calories: total.calories + meal.nutrition.calories,
405
705
  protein: total.protein + meal.nutrition.protein,
406
706
  carbs: total.carbs + meal.nutrition.carbs,
407
707
  fat: total.fat + meal.nutrition.fat
408
708
  }), { calories: 0, protein: 0, carbs: 0, fat: 0 });
409
- const prompt = `Analyze this meal plan for a person with the following profile:
410
- ${userContext}
411
-
412
- Meal Plan Summary:
413
- - Total meals: ${meals.length}
414
- - Total calories: ${totalNutrition.calories}
415
- - Total protein: ${totalNutrition.protein}g
416
- - Total carbs: ${totalNutrition.carbs}g
417
- - Total fat: ${totalNutrition.fat}g
418
-
419
- Meals included:
420
- ${meals.map(meal => `- ${meal.name} (${meal.category}): ${meal.nutrition.calories} cal`).join('\n')}
421
-
422
- Provide:
423
- 1. A comprehensive nutritional analysis
424
- 2. Specific recommendations for this user
709
+ const prompt = isBosnian
710
+ ? `Analiziraj ovaj plan obroka za osobu sa sljedećim profilom:
711
+ ${userContext}
712
+
713
+ Sažetak plana obroka:
714
+ - Ukupno obroka: ${meals.length}
715
+ - Ukupno kalorija: ${totalNutrition.calories}
716
+ - Ukupno proteina: ${totalNutrition.protein}g
717
+ - Ukupno ugljenih hidrata: ${totalNutrition.carbs}g
718
+ - Ukupno masti: ${totalNutrition.fat}g
719
+
720
+ Uključeni obroci:
721
+ ${meals.map(meal => `- ${meal.name} (${meal.category}): ${meal.nutrition.calories} kal`).join('\n')}
722
+
723
+ Obezbedi:
724
+ 1. Sveobuhvatnu nutricionu analizu
725
+ 2. Specifične preporuke za ovog korisnika
726
+ 3. Konkretne poboljšanja koja se mogu učiniti. Sva analiza, preporuke i poboljšanja moraju biti na bosanskom jeziku.`
727
+ : `Analyze this meal plan for a person with the following profile:
728
+ ${userContext}
729
+
730
+ Meal Plan Summary:
731
+ - Total meals: ${meals.length}
732
+ - Total calories: ${totalNutrition.calories}
733
+ - Total protein: ${totalNutrition.protein}g
734
+ - Total carbs: ${totalNutrition.carbs}g
735
+ - Total fat: ${totalNutrition.fat}g
736
+
737
+ Meals included:
738
+ ${meals.map(meal => `- ${meal.name} (${meal.category}): ${meal.nutrition.calories} cal`).join('\n')}
739
+
740
+ Provide:
741
+ 1. A comprehensive nutritional analysis
742
+ 2. Specific recommendations for this user
425
743
  3. Concrete improvements that could be made`;
426
744
  try {
427
745
  const response = await this.makeRequest(prompt, systemPrompt);
@@ -107,10 +107,10 @@ export interface AIResponse<T = any> {
107
107
  };
108
108
  }
109
109
  export interface AIServiceInterface {
110
- generateMealPlan(request: AIMealPlanRequest): Promise<AIResponse<AIGeneratedMealPlan>>;
111
- generateRecipe(request: AIRecipeRequest): Promise<AIResponse<AIGeneratedMeal>>;
110
+ generateMealPlan(request: AIMealPlanRequest, language?: string): Promise<AIResponse<AIGeneratedMealPlan>>;
111
+ generateRecipe(request: AIRecipeRequest, language?: string): Promise<AIResponse<AIGeneratedMeal>>;
112
112
  suggestMealVariations(meal: AIGeneratedMeal, context: AIPromptContext): Promise<AIResponse<AIGeneratedMeal[]>>;
113
- analyzeMealPlan(meals: AIGeneratedMeal[], context: AIPromptContext): Promise<AIResponse<{
113
+ analyzeMealPlan(meals: AIGeneratedMeal[], context: AIPromptContext, language?: string): Promise<AIResponse<{
114
114
  nutritionalAnalysis: string;
115
115
  recommendations: string[];
116
116
  improvements: string[];
@@ -0,0 +1,137 @@
1
+ {
2
+ "common": {
3
+ "appName": "Healthy Meals",
4
+ "loading": "Učitavanje...",
5
+ "error": "Greška",
6
+ "save": "Sačuvaj",
7
+ "cancel": "Otkaži",
8
+ "next": "Dalje",
9
+ "back": "Nazad",
10
+ "finish": "Završi",
11
+ "edit": "Uredi",
12
+ "delete": "Obriši",
13
+ "close": "Zatvori",
14
+ "yes": "Da",
15
+ "no": "Ne",
16
+ "ok": "OK"
17
+ },
18
+ "welcome": {
19
+ "title": "🥗 Healthy Meals",
20
+ "subtitle": "Personalizirani planovi ishrane za vaše zdravlje",
21
+ "start": "Započni"
22
+ },
23
+ "onboarding": {
24
+ "step1": {
25
+ "title": "Osnovni podaci",
26
+ "subtitle": "Unesite vaše fizičke parametre",
27
+ "height": "Visina (cm)",
28
+ "weight": "Težina (kg)",
29
+ "age": "Dob (godine)",
30
+ "gender": "Pol",
31
+ "male": "Muško",
32
+ "female": "Žensko"
33
+ },
34
+ "step2": {
35
+ "title": "Aktivnost i cilj",
36
+ "subtitle": "Kako aktivni ste i šta želite postići?",
37
+ "activityLevel": "Nivo aktivnosti",
38
+ "activityLevels": {
39
+ "sedentary": "Sedentarni (bez vježbanja)",
40
+ "light": "Lako aktivan (1-3 dana/sedmicu)",
41
+ "moderate": "Umjereno aktivan (3-5 dana/sedmicu)",
42
+ "very": "Vrlo aktivan (6-7 dana/sedmicu)"
43
+ },
44
+ "goal": "Cilj",
45
+ "goals": {
46
+ "lose": "Gubitak težine",
47
+ "maintain": "Održavanje težine",
48
+ "gain": "Dobijanje težine"
49
+ }
50
+ },
51
+ "step3": {
52
+ "title": "Zdravstveno stanje",
53
+ "subtitle": "Izaberite sve što se odnosi na vas (opcionalno)",
54
+ "conditions": {
55
+ "cholesterol": "Visoki holesterol",
56
+ "triglycerides": "Visoki trigliceridi",
57
+ "fatty_liver": "Masna jetra",
58
+ "diabetes": "Dijabetes",
59
+ "low_carb": "Low-carb dijeta"
60
+ }
61
+ }
62
+ },
63
+ "dashboard": {
64
+ "title": "Vaš Plan Ishrane",
65
+ "daily": "Dnevni Plan",
66
+ "weekly": "Sedmični Plan",
67
+ "totalCalories": "Ukupno kalorija",
68
+ "meals": "obroka",
69
+ "days": "dana",
70
+ "meal": "Obrok",
71
+ "calories": "kalorija",
72
+ "macros": {
73
+ "protein": "Proteini",
74
+ "fat": "Masti",
75
+ "carbs": "Ugljikohidrati"
76
+ },
77
+ "stats": {
78
+ "bmr": "BMR",
79
+ "tdee": "TDEE",
80
+ "unit": "kcal/dan",
81
+ "conditions": "aktivnih"
82
+ }
83
+ },
84
+ "profile": {
85
+ "title": "Moj Profil",
86
+ "basicInfo": "Osnovni Podaci",
87
+ "metabolism": "Metabolizam",
88
+ "activityGoal": "Aktivnost & Cilj",
89
+ "healthConditions": "Zdravstveno Stanje",
90
+ "editProfile": "Uredi Profil"
91
+ },
92
+ "landing": {
93
+ "hero": {
94
+ "title": "Personalizirani Planovi Ishrane",
95
+ "subtitle": "Besplatna aplikacija koja generiše zdrave obroke prilagođene vašim zdravstvenim potrebama, fizičkim parametrima i ciljevima.",
96
+ "cta": "Kreiraj Profil →"
97
+ },
98
+ "features": {
99
+ "personalized": {
100
+ "title": "Personalizovano",
101
+ "description": "Planovi bazirani na vašoj visini, težini, dobi, polu i nivou aktivnosti"
102
+ },
103
+ "health": {
104
+ "title": "Zdravstveno Prilagođeno",
105
+ "description": "Podrška za holesterol, trigliceride, masnu jetru, dijabetes i low-carb"
106
+ },
107
+ "weekly": {
108
+ "title": "Dnevni & Sedmični",
109
+ "description": "Generiši planove za jedan dan ili cijelu sedmicu sa raznolikim obrocima"
110
+ }
111
+ },
112
+ "howItWorks": {
113
+ "title": "Kako Radi?",
114
+ "steps": {
115
+ "1": "Unesite osnovne podatke (visina, težina, dob, pol)",
116
+ "2": "Izaberite nivo aktivnosti i cilj (mršavljenje/održavanje/dobijanje)",
117
+ "3": "Označite zdravstvena stanja (opcionalno)",
118
+ "4": "Dobijte personalizovani plan ishrane sa nutritivnim podacima"
119
+ }
120
+ },
121
+ "benefits": {
122
+ "title": "Besplatno & Open Source",
123
+ "points": {
124
+ "free": "100% besplatno, bez skrivenih troškova",
125
+ "science": "Bazirana na naučnim formulama (Mifflin-St Jeor)",
126
+ "recipes": "Kurirana baza zdravih recepata",
127
+ "platforms": "Dostupno na webu i mobilnim uređajima"
128
+ }
129
+ },
130
+ "footer": "Made with ❤️ for healthy living"
131
+ },
132
+ "languages": {
133
+ "select": "Izaberite jezik",
134
+ "bosnian": "Bosanski",
135
+ "english": "English"
136
+ }
137
+ }
@@ -0,0 +1,137 @@
1
+ {
2
+ "common": {
3
+ "appName": "Healthy Meals",
4
+ "loading": "Loading...",
5
+ "error": "Error",
6
+ "save": "Save",
7
+ "cancel": "Cancel",
8
+ "next": "Next",
9
+ "back": "Back",
10
+ "finish": "Finish",
11
+ "edit": "Edit",
12
+ "delete": "Delete",
13
+ "close": "Close",
14
+ "yes": "Yes",
15
+ "no": "No",
16
+ "ok": "OK"
17
+ },
18
+ "welcome": {
19
+ "title": "🥗 Healthy Meals",
20
+ "subtitle": "Personalized meal plans for your health",
21
+ "start": "Get Started"
22
+ },
23
+ "onboarding": {
24
+ "step1": {
25
+ "title": "Basic Information",
26
+ "subtitle": "Enter your physical parameters",
27
+ "height": "Height (cm)",
28
+ "weight": "Weight (kg)",
29
+ "age": "Age (years)",
30
+ "gender": "Gender",
31
+ "male": "Male",
32
+ "female": "Female"
33
+ },
34
+ "step2": {
35
+ "title": "Activity & Goal",
36
+ "subtitle": "How active are you and what do you want to achieve?",
37
+ "activityLevel": "Activity Level",
38
+ "activityLevels": {
39
+ "sedentary": "Sedentary (no exercise)",
40
+ "light": "Lightly active (1-3 days/week)",
41
+ "moderate": "Moderately active (3-5 days/week)",
42
+ "very": "Very active (6-7 days/week)"
43
+ },
44
+ "goal": "Goal",
45
+ "goals": {
46
+ "lose": "Weight Loss",
47
+ "maintain": "Weight Maintenance",
48
+ "gain": "Weight Gain"
49
+ }
50
+ },
51
+ "step3": {
52
+ "title": "Health Conditions",
53
+ "subtitle": "Select all that apply to you (optional)",
54
+ "conditions": {
55
+ "cholesterol": "High Cholesterol",
56
+ "triglycerides": "High Triglycerides",
57
+ "fatty_liver": "Fatty Liver",
58
+ "diabetes": "Diabetes",
59
+ "low_carb": "Low-carb Diet"
60
+ }
61
+ }
62
+ },
63
+ "dashboard": {
64
+ "title": "Your Meal Plan",
65
+ "daily": "Daily Plan",
66
+ "weekly": "Weekly Plan",
67
+ "totalCalories": "Total Calories",
68
+ "meals": "meals",
69
+ "days": "days",
70
+ "meal": "Meal",
71
+ "calories": "calories",
72
+ "macros": {
73
+ "protein": "Protein",
74
+ "fat": "Fat",
75
+ "carbs": "Carbohydrates"
76
+ },
77
+ "stats": {
78
+ "bmr": "BMR",
79
+ "tdee": "TDEE",
80
+ "unit": "kcal/day",
81
+ "conditions": "active"
82
+ }
83
+ },
84
+ "profile": {
85
+ "title": "My Profile",
86
+ "basicInfo": "Basic Information",
87
+ "metabolism": "Metabolism",
88
+ "activityGoal": "Activity & Goal",
89
+ "healthConditions": "Health Conditions",
90
+ "editProfile": "Edit Profile"
91
+ },
92
+ "landing": {
93
+ "hero": {
94
+ "title": "Personalized Meal Plans",
95
+ "subtitle": "Free app that generates healthy meals tailored to your health needs, physical parameters, and goals.",
96
+ "cta": "Create Profile →"
97
+ },
98
+ "features": {
99
+ "personalized": {
100
+ "title": "Personalized",
101
+ "description": "Plans based on your height, weight, age, gender, and activity level"
102
+ },
103
+ "health": {
104
+ "title": "Health Adapted",
105
+ "description": "Support for cholesterol, triglycerides, fatty liver, diabetes, and low-carb"
106
+ },
107
+ "weekly": {
108
+ "title": "Daily & Weekly",
109
+ "description": "Generate plans for one day or entire week with varied meals"
110
+ }
111
+ },
112
+ "howItWorks": {
113
+ "title": "How It Works?",
114
+ "steps": {
115
+ "1": "Enter basic information (height, weight, age, gender)",
116
+ "2": "Choose activity level and goal (lose/maintain/gain weight)",
117
+ "3": "Select health conditions (optional)",
118
+ "4": "Get personalized meal plan with nutritional data"
119
+ }
120
+ },
121
+ "benefits": {
122
+ "title": "Free & Open Source",
123
+ "points": {
124
+ "free": "100% free, no hidden costs",
125
+ "science": "Based on scientific formulas (Mifflin-St Jeor)",
126
+ "recipes": "Curated database of healthy recipes",
127
+ "platforms": "Available on web and mobile devices"
128
+ }
129
+ },
130
+ "footer": "Made with ❤️ for healthy living"
131
+ },
132
+ "languages": {
133
+ "select": "Select Language",
134
+ "bosnian": "Bosanski",
135
+ "english": "English"
136
+ }
137
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usamir/healthy-meals-core",
3
- "version": "0.0.16",
3
+ "version": "0.0.18",
4
4
  "main": "dist/src/index.js",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "exports": {
@@ -71,7 +71,9 @@
71
71
  "./types/firestore": {
72
72
  "types": "./dist/src/types/firestore.d.ts",
73
73
  "default": "./dist/src/types/firestore.js"
74
- }
74
+ },
75
+ "./locales/bs.json": "./locales/bs.json",
76
+ "./locales/en.json": "./locales/en.json"
75
77
  },
76
78
  "scripts": {
77
79
  "build": "npx tsc",
@@ -80,7 +82,8 @@
80
82
  },
81
83
  "files": [
82
84
  "dist/**/*",
83
- "data/**/*"
85
+ "data/**/*",
86
+ "locales/**/*"
84
87
  ],
85
88
  "publishConfig": {
86
89
  "access": "public"