@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.
- package/dist/src/services/aiMealPlanGenerator.d.ts +6 -1
- package/dist/src/services/aiMealPlanGenerator.js +5 -5
- package/dist/src/services/unifiedAIService.d.ts +6 -3
- package/dist/src/services/unifiedAIService.js +503 -185
- package/dist/src/types/ai.d.ts +3 -3
- package/locales/bs.json +137 -0
- package/locales/en.json +137 -0
- package/package.json +6 -3
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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 +=
|
|
85
|
-
|
|
86
|
-
|
|
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 +=
|
|
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
|
-
|
|
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
|
-
|
|
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 +=
|
|
175
|
+
profileText += isBosnian
|
|
176
|
+
? `Alergije: ${userProfile.allergies.join(', ')}. `
|
|
177
|
+
: `Allergies: ${userProfile.allergies.join(', ')}. `;
|
|
100
178
|
}
|
|
101
179
|
if (userProfile.healthConditions?.length) {
|
|
102
|
-
profileText +=
|
|
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 +=
|
|
185
|
+
profileText += isBosnian
|
|
186
|
+
? `Preferirane kuhinje: ${preferences.cuisineTypes.join(', ')}. `
|
|
187
|
+
: `Preferred cuisines: ${preferences.cuisineTypes.join(', ')}. `;
|
|
106
188
|
}
|
|
107
189
|
if (preferences?.cookingTime) {
|
|
108
|
-
profileText +=
|
|
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 +=
|
|
195
|
+
profileText += isBosnian
|
|
196
|
+
? `Nutritivni ciljevi: `
|
|
197
|
+
: `Nutritional targets: `;
|
|
112
198
|
if (nutritionalTargets.calories)
|
|
113
|
-
profileText +=
|
|
199
|
+
profileText += isBosnian
|
|
200
|
+
? `${nutritionalTargets.calories} kalorija, `
|
|
201
|
+
: `${nutritionalTargets.calories} calories, `;
|
|
114
202
|
if (nutritionalTargets.protein)
|
|
115
|
-
profileText +=
|
|
203
|
+
profileText += isBosnian
|
|
204
|
+
? `${nutritionalTargets.protein}g proteina, `
|
|
205
|
+
: `${nutritionalTargets.protein}g protein, `;
|
|
116
206
|
if (nutritionalTargets.carbs)
|
|
117
|
-
profileText +=
|
|
207
|
+
profileText += isBosnian
|
|
208
|
+
? `${nutritionalTargets.carbs}g ugljiko hidrata, `
|
|
209
|
+
: `${nutritionalTargets.carbs}g carbs, `;
|
|
118
210
|
if (nutritionalTargets.fat)
|
|
119
|
-
profileText +=
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
165
|
-
fixed = fixed.replace(/([
|
|
166
|
-
// 3. Fix
|
|
167
|
-
fixed = fixed.replace(/
|
|
168
|
-
|
|
169
|
-
fixed = fixed.replace(
|
|
170
|
-
|
|
171
|
-
fixed = fixed.replace(/"
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
217
|
-
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
232
|
-
|
|
233
|
-
|
|
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
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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 =
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
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 +=
|
|
305
|
-
|
|
306
|
-
|
|
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
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"
|
|
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 =
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
-
|
|
415
|
-
-
|
|
416
|
-
-
|
|
417
|
-
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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);
|
package/dist/src/types/ai.d.ts
CHANGED
|
@@ -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[];
|
package/locales/bs.json
ADDED
|
@@ -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
|
+
}
|
package/locales/en.json
ADDED
|
@@ -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.
|
|
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"
|