@usamir/healthy-meals-core 0.0.15 → 0.0.17
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/data/detailed-recipes-bs.json +4 -4
- package/data/recipes.json +1 -1
- package/dist/data/detailed-recipes-bs.json +4 -4
- package/dist/src/services/aiMealPlanGenerator.d.ts +1 -1
- package/dist/src/services/aiMealPlanGenerator.js +7 -7
- package/dist/src/services/ollamaService.d.ts +1 -0
- package/dist/src/services/ollamaService.js +68 -8
- package/dist/src/services/unifiedAIService.d.ts +26 -0
- package/dist/src/services/unifiedAIService.js +448 -0
- package/dist/src/types/ai.d.ts +1 -0
- package/package.json +5 -1
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
{ "name": "File lososa", "quantity": 150, "unit": "g" },
|
|
227
227
|
{ "name": "Smeđa riža", "quantity": 75, "unit": "g" },
|
|
228
228
|
{ "name": "Brokoli", "quantity": 100, "unit": "g" },
|
|
229
|
-
{ "name": "
|
|
229
|
+
{ "name": "Mrkva", "quantity": 80, "unit": "g" },
|
|
230
230
|
{ "name": "Edamame", "quantity": 50, "unit": "g" },
|
|
231
231
|
{ "name": "Sezamovo ulje", "quantity": 1, "unit": "žličica" },
|
|
232
232
|
{ "name": "Soja sos", "quantity": 1, "unit": "žličica" },
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
],
|
|
236
236
|
"instructions": [
|
|
237
237
|
"Skuhajte smeđu rižu prema uputstvima na pakovanju",
|
|
238
|
-
"Na pari skuhamo brokoli i narezanu
|
|
238
|
+
"Na pari skuhamo brokoli i narezanu mrkvu 5-7 minuta",
|
|
239
239
|
"Začinite lososa đumbirom, so i biberom",
|
|
240
240
|
"Pržite lososa u sezamovom ulju po 4-5 minuta sa svake strane",
|
|
241
241
|
"Rasporedite rižu u zdjelu",
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
"difficulty": "easy",
|
|
343
343
|
"ingredients": [
|
|
344
344
|
{ "name": "Crvena leća", "quantity": 150, "unit": "g" },
|
|
345
|
-
{ "name": "
|
|
345
|
+
{ "name": "Mrkva", "quantity": 100, "unit": "g" },
|
|
346
346
|
{ "name": "Celer", "quantity": 80, "unit": "g" },
|
|
347
347
|
{ "name": "Luk", "quantity": 1, "unit": "srednji" },
|
|
348
348
|
{ "name": "Bijeli luk", "quantity": 3, "unit": "čehnjaka" },
|
|
@@ -354,7 +354,7 @@
|
|
|
354
354
|
],
|
|
355
355
|
"instructions": [
|
|
356
356
|
"Zagrijte maslinovo ulje u velikoj posudi na srednjoj vatri",
|
|
357
|
-
"Pržite narezani luk,
|
|
357
|
+
"Pržite narezani luk, mrkvu i celer 5 minuta",
|
|
358
358
|
"Dodajte sjeckani bijeli luk i kumin, kuhajte još 1 minutu",
|
|
359
359
|
"Dodajte leću, narezane paradajze i povrtnu supu",
|
|
360
360
|
"Zavrijte, zatim smanjite vatru i pustite da se krčka",
|
package/data/recipes.json
CHANGED
|
@@ -226,7 +226,7 @@
|
|
|
226
226
|
{ "name": "File lososa", "quantity": 150, "unit": "g" },
|
|
227
227
|
{ "name": "Smeđa riža", "quantity": 75, "unit": "g" },
|
|
228
228
|
{ "name": "Brokoli", "quantity": 100, "unit": "g" },
|
|
229
|
-
{ "name": "
|
|
229
|
+
{ "name": "Mrkva", "quantity": 80, "unit": "g" },
|
|
230
230
|
{ "name": "Edamame", "quantity": 50, "unit": "g" },
|
|
231
231
|
{ "name": "Sezamovo ulje", "quantity": 1, "unit": "žličica" },
|
|
232
232
|
{ "name": "Soja sos", "quantity": 1, "unit": "žličica" },
|
|
@@ -235,7 +235,7 @@
|
|
|
235
235
|
],
|
|
236
236
|
"instructions": [
|
|
237
237
|
"Skuhajte smeđu rižu prema uputstvima na pakovanju",
|
|
238
|
-
"Na pari skuhamo brokoli i narezanu
|
|
238
|
+
"Na pari skuhamo brokoli i narezanu mrkvu 5-7 minuta",
|
|
239
239
|
"Začinite lososa đumbirom, so i biberom",
|
|
240
240
|
"Pržite lososa u sezamovom ulju po 4-5 minuta sa svake strane",
|
|
241
241
|
"Rasporedite rižu u zdjelu",
|
|
@@ -342,7 +342,7 @@
|
|
|
342
342
|
"difficulty": "easy",
|
|
343
343
|
"ingredients": [
|
|
344
344
|
{ "name": "Crvena leća", "quantity": 150, "unit": "g" },
|
|
345
|
-
{ "name": "
|
|
345
|
+
{ "name": "Mrkva", "quantity": 100, "unit": "g" },
|
|
346
346
|
{ "name": "Celer", "quantity": 80, "unit": "g" },
|
|
347
347
|
{ "name": "Luk", "quantity": 1, "unit": "srednji" },
|
|
348
348
|
{ "name": "Bijeli luk", "quantity": 3, "unit": "čehnjaka" },
|
|
@@ -354,7 +354,7 @@
|
|
|
354
354
|
],
|
|
355
355
|
"instructions": [
|
|
356
356
|
"Zagrijte maslinovo ulje u velikoj posudi na srednjoj vatri",
|
|
357
|
-
"Pržite narezani luk,
|
|
357
|
+
"Pržite narezani luk, mrkvu i celer 5 minuta",
|
|
358
358
|
"Dodajte sjeckani bijeli luk i kumin, kuhajte još 1 minutu",
|
|
359
359
|
"Dodajte leću, narezane paradajze i povrtnu supu",
|
|
360
360
|
"Zavrijte, zatim smanjite vatru i pustite da se krčka",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { UserProfile, MealPlan, Meal } from '../types/firestore';
|
|
2
2
|
import { OllamaConfig } from '../types/ai';
|
|
3
3
|
export declare class AIMealPlanGenerator {
|
|
4
|
-
private
|
|
4
|
+
private aiService;
|
|
5
5
|
constructor(ollamaConfig: OllamaConfig);
|
|
6
6
|
private convertUserProfileToAIContext;
|
|
7
7
|
private convertAIMealToMeal;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AIMealPlanGenerator = void 0;
|
|
4
|
-
const
|
|
4
|
+
const unifiedAIService_1 = require("./unifiedAIService");
|
|
5
5
|
class AIMealPlanGenerator {
|
|
6
6
|
constructor(ollamaConfig) {
|
|
7
|
-
this.
|
|
7
|
+
this.aiService = new unifiedAIService_1.UnifiedAIService(ollamaConfig);
|
|
8
8
|
}
|
|
9
9
|
convertUserProfileToAIContext(profile) {
|
|
10
10
|
return {
|
|
@@ -124,7 +124,7 @@ class AIMealPlanGenerator {
|
|
|
124
124
|
avoidIngredients: options?.avoidIngredients,
|
|
125
125
|
focusAreas: options?.focusAreas
|
|
126
126
|
};
|
|
127
|
-
const response = await this.
|
|
127
|
+
const response = await this.aiService.generateMealPlan(request);
|
|
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.
|
|
142
|
+
const response = await this.aiService.generateMealPlan(request);
|
|
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.
|
|
158
|
+
const response = await this.aiService.generateRecipe(request);
|
|
159
159
|
if (!response.success || !response.data) {
|
|
160
160
|
throw new Error(`Failed to generate AI recipe: ${response.error}`);
|
|
161
161
|
}
|
|
@@ -190,7 +190,7 @@ class AIMealPlanGenerator {
|
|
|
190
190
|
tags: meal.tags,
|
|
191
191
|
tips: meal.tips
|
|
192
192
|
};
|
|
193
|
-
const response = await this.
|
|
193
|
+
const response = await this.aiService.suggestMealVariations(aiMeal, context);
|
|
194
194
|
if (!response.success || !response.data) {
|
|
195
195
|
throw new Error(`Failed to generate meal variations: ${response.error}`);
|
|
196
196
|
}
|
|
@@ -225,7 +225,7 @@ class AIMealPlanGenerator {
|
|
|
225
225
|
tags: meal.tags,
|
|
226
226
|
tips: meal.tips
|
|
227
227
|
}));
|
|
228
|
-
const response = await this.
|
|
228
|
+
const response = await this.aiService.analyzeMealPlan(aiMeals, context);
|
|
229
229
|
if (!response.success || !response.data) {
|
|
230
230
|
throw new Error(`Failed to analyze meal plan: ${response.error}`);
|
|
231
231
|
}
|
|
@@ -5,6 +5,7 @@ export declare class OllamaService implements AIServiceInterface {
|
|
|
5
5
|
private makeRequest;
|
|
6
6
|
private buildUserProfileContext;
|
|
7
7
|
private parseJSONResponse;
|
|
8
|
+
private fixCommonJSONIssues;
|
|
8
9
|
generateMealPlan(request: AIMealPlanRequest): Promise<AIResponse<AIGeneratedMealPlan>>;
|
|
9
10
|
generateRecipe(request: AIRecipeRequest): Promise<AIResponse<AIGeneratedMeal>>;
|
|
10
11
|
suggestMealVariations(meal: AIGeneratedMeal, context: AIPromptContext): Promise<AIResponse<AIGeneratedMeal[]>>;
|
|
@@ -87,20 +87,60 @@ class OllamaService {
|
|
|
87
87
|
}
|
|
88
88
|
parseJSONResponse(response) {
|
|
89
89
|
try {
|
|
90
|
+
// Clean up the response - remove any markdown code blocks
|
|
91
|
+
let cleanedResponse = response.replace(/```json\s*/g, '').replace(/```\s*$/g, '').trim();
|
|
90
92
|
// Try to find JSON in the response
|
|
91
|
-
const jsonMatch =
|
|
93
|
+
const jsonMatch = cleanedResponse.match(/\{[\s\S]*\}/);
|
|
92
94
|
if (jsonMatch) {
|
|
93
|
-
|
|
95
|
+
const jsonString = jsonMatch[0];
|
|
96
|
+
// Additional validation to ensure proper JSON structure
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(jsonString);
|
|
99
|
+
return parsed;
|
|
100
|
+
}
|
|
101
|
+
catch (parseError) {
|
|
102
|
+
// If direct parsing fails, try to fix common JSON issues
|
|
103
|
+
const fixedJson = this.fixCommonJSONIssues(jsonString);
|
|
104
|
+
return JSON.parse(fixedJson);
|
|
105
|
+
}
|
|
94
106
|
}
|
|
95
107
|
// If no JSON found, try parsing the entire response
|
|
96
|
-
|
|
108
|
+
try {
|
|
109
|
+
return JSON.parse(cleanedResponse);
|
|
110
|
+
}
|
|
111
|
+
catch (parseError) {
|
|
112
|
+
// Try to fix the entire response
|
|
113
|
+
const fixedResponse = this.fixCommonJSONIssues(cleanedResponse);
|
|
114
|
+
return JSON.parse(fixedResponse);
|
|
115
|
+
}
|
|
97
116
|
}
|
|
98
117
|
catch (error) {
|
|
99
|
-
|
|
118
|
+
// Log the actual response for debugging
|
|
119
|
+
console.error('Failed to parse AI response. Raw response:', response);
|
|
120
|
+
throw new Error(`Failed to parse AI response as JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
100
121
|
}
|
|
101
122
|
}
|
|
123
|
+
fixCommonJSONIssues(jsonString) {
|
|
124
|
+
let fixed = jsonString;
|
|
125
|
+
// Fix common issues with AI-generated JSON
|
|
126
|
+
// 1. Fix trailing commas in arrays and objects
|
|
127
|
+
fixed = fixed.replace(/,\s*([}\]])/g, '$1');
|
|
128
|
+
// 2. Fix unquoted property names
|
|
129
|
+
fixed = fixed.replace(/([{\s,])([a-zA-Z_][a-zA-Z0-9_]*)\s*:/g, '$1"$2":');
|
|
130
|
+
// 3. Fix single quotes instead of double quotes
|
|
131
|
+
fixed = fixed.replace(/'/g, '"');
|
|
132
|
+
// 4. Fix missing quotes around string values
|
|
133
|
+
fixed = fixed.replace(/:\s*([a-zA-Z_][a-zA-Z0-9_]*)([\s,}])/g, ': "$1"$2');
|
|
134
|
+
// 5. Fix line breaks in strings
|
|
135
|
+
fixed = fixed.replace(/"([^"]*)\n([^"]*)"/g, '"$1\\n$2"');
|
|
136
|
+
// 6. Fix escaped quotes that might be double-escaped
|
|
137
|
+
fixed = fixed.replace(/\\\\"/g, '\\"');
|
|
138
|
+
return fixed;
|
|
139
|
+
}
|
|
102
140
|
async generateMealPlan(request) {
|
|
103
|
-
const systemPrompt = `You are a professional nutritionist and meal planning expert. Generate personalized meal plans based on user profiles, dietary restrictions, and health goals.
|
|
141
|
+
const systemPrompt = `You are a professional nutritionist and meal planning expert. Generate personalized meal plans based on user profiles, dietary restrictions, and health goals.
|
|
142
|
+
|
|
143
|
+
CRITICAL: Respond with ONLY valid JSON. No explanations, no markdown formatting, no additional text. Your entire response must be a single JSON object.
|
|
104
144
|
|
|
105
145
|
The JSON response must follow this exact structure:
|
|
106
146
|
{
|
|
@@ -126,7 +166,9 @@ The JSON response must follow this exact structure:
|
|
|
126
166
|
"totalNutrition": {"calories": number, "protein": number, "carbs": number, "fat": number},
|
|
127
167
|
"recommendations": ["string"],
|
|
128
168
|
"shoppingList": [{"ingredient": "string", "quantity": number, "unit": "string", "category": "string"}]
|
|
129
|
-
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
IMPORTANT: Ensure all strings are properly double-quoted, numbers are not quoted, and there are no trailing commas.`;
|
|
130
172
|
const userContext = this.buildUserProfileContext(request.context);
|
|
131
173
|
const planType = request.type === 'daily' ? 'one day' : 'seven days';
|
|
132
174
|
let prompt = `Create a ${planType} meal plan for a person with the following profile:\n${userContext}\n\n`;
|
|
@@ -148,7 +190,13 @@ The JSON response must follow this exact structure:
|
|
|
148
190
|
prompt += `Ensure nutritional balance, variety, and alignment with the user's goals. Include detailed cooking instructions, accurate nutritional information, and practical tips.`;
|
|
149
191
|
try {
|
|
150
192
|
const response = await this.makeRequest(prompt, systemPrompt);
|
|
193
|
+
// Log the raw response for debugging
|
|
194
|
+
console.log('Raw AI meal plan response:', response.response);
|
|
151
195
|
const parsedData = this.parseJSONResponse(response.response);
|
|
196
|
+
// Validate the parsed data has required fields
|
|
197
|
+
if (!parsedData.name || !parsedData.meals || !Array.isArray(parsedData.meals)) {
|
|
198
|
+
throw new Error('Invalid meal plan structure: missing required fields');
|
|
199
|
+
}
|
|
152
200
|
return {
|
|
153
201
|
success: true,
|
|
154
202
|
data: parsedData,
|
|
@@ -160,6 +208,7 @@ The JSON response must follow this exact structure:
|
|
|
160
208
|
};
|
|
161
209
|
}
|
|
162
210
|
catch (error) {
|
|
211
|
+
console.error('Meal plan generation error:', error);
|
|
163
212
|
return {
|
|
164
213
|
success: false,
|
|
165
214
|
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
@@ -167,7 +216,9 @@ The JSON response must follow this exact structure:
|
|
|
167
216
|
}
|
|
168
217
|
}
|
|
169
218
|
async generateRecipe(request) {
|
|
170
|
-
const systemPrompt = `You are a professional chef and nutritionist. Generate detailed, healthy recipes based on user requirements.
|
|
219
|
+
const systemPrompt = `You are a professional chef and nutritionist. Generate detailed, healthy recipes based on user requirements.
|
|
220
|
+
|
|
221
|
+
CRITICAL: Respond with ONLY valid JSON. No explanations, no markdown formatting, no additional text. Your entire response must be a single JSON object.
|
|
171
222
|
|
|
172
223
|
The JSON response must follow this exact structure:
|
|
173
224
|
{
|
|
@@ -183,7 +234,9 @@ The JSON response must follow this exact structure:
|
|
|
183
234
|
"difficulty": "easy" | "medium" | "hard",
|
|
184
235
|
"tags": ["string"],
|
|
185
236
|
"tips": ["string"]
|
|
186
|
-
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
IMPORTANT: Ensure all strings are properly double-quoted, numbers are not quoted, and there are no trailing commas.`;
|
|
187
240
|
const userContext = this.buildUserProfileContext(request.context);
|
|
188
241
|
let prompt = `Create a ${request.mealType} recipe for a person with the following profile:\n${userContext}\n\n`;
|
|
189
242
|
if (request.ingredients?.length) {
|
|
@@ -201,7 +254,13 @@ The JSON response must follow this exact structure:
|
|
|
201
254
|
prompt += `\nGenerate a detailed recipe with accurate nutritional information, clear instructions, and helpful cooking tips.`;
|
|
202
255
|
try {
|
|
203
256
|
const response = await this.makeRequest(prompt, systemPrompt);
|
|
257
|
+
// Log the raw response for debugging
|
|
258
|
+
console.log('Raw AI recipe response:', response.response);
|
|
204
259
|
const parsedData = this.parseJSONResponse(response.response);
|
|
260
|
+
// Validate the parsed data has required fields
|
|
261
|
+
if (!parsedData.name || !parsedData.ingredients || !parsedData.instructions) {
|
|
262
|
+
throw new Error('Invalid recipe structure: missing required fields');
|
|
263
|
+
}
|
|
205
264
|
return {
|
|
206
265
|
success: true,
|
|
207
266
|
data: parsedData,
|
|
@@ -213,6 +272,7 @@ The JSON response must follow this exact structure:
|
|
|
213
272
|
};
|
|
214
273
|
}
|
|
215
274
|
catch (error) {
|
|
275
|
+
console.error('Recipe generation error:', error);
|
|
216
276
|
return {
|
|
217
277
|
success: false,
|
|
218
278
|
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AIServiceInterface, AIMealPlanRequest, AIRecipeRequest, AIGeneratedMeal, AIGeneratedMealPlan, AIResponse, AIPromptContext } from '../types/ai';
|
|
2
|
+
interface AIConfig {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
model: string;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class UnifiedAIService implements AIServiceInterface {
|
|
9
|
+
private config;
|
|
10
|
+
private isGroq;
|
|
11
|
+
constructor(config: AIConfig);
|
|
12
|
+
private makeRequest;
|
|
13
|
+
private extractResponseContent;
|
|
14
|
+
private buildUserProfileContext;
|
|
15
|
+
private parseJSONResponse;
|
|
16
|
+
private fixCommonJSONIssues;
|
|
17
|
+
generateMealPlan(request: AIMealPlanRequest): Promise<AIResponse<AIGeneratedMealPlan>>;
|
|
18
|
+
generateRecipe(request: AIRecipeRequest): Promise<AIResponse<AIGeneratedMeal>>;
|
|
19
|
+
suggestMealVariations(meal: AIGeneratedMeal, context: AIPromptContext): Promise<AIResponse<AIGeneratedMeal[]>>;
|
|
20
|
+
analyzeMealPlan(meals: AIGeneratedMeal[], context: AIPromptContext): Promise<AIResponse<{
|
|
21
|
+
nutritionalAnalysis: string;
|
|
22
|
+
recommendations: string[];
|
|
23
|
+
improvements: string[];
|
|
24
|
+
}>>;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnifiedAIService = void 0;
|
|
4
|
+
class UnifiedAIService {
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = {
|
|
7
|
+
timeout: 60000,
|
|
8
|
+
...config
|
|
9
|
+
};
|
|
10
|
+
this.isGroq = config.baseUrl.includes('groq.com') || config.baseUrl.includes('openai.com');
|
|
11
|
+
}
|
|
12
|
+
async makeRequest(prompt, systemPrompt) {
|
|
13
|
+
let requestBody;
|
|
14
|
+
let endpoint;
|
|
15
|
+
let headers = {
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
};
|
|
18
|
+
if (this.isGroq) {
|
|
19
|
+
// Groq/OpenAI API format
|
|
20
|
+
requestBody = {
|
|
21
|
+
model: this.config.model,
|
|
22
|
+
messages: [
|
|
23
|
+
...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
|
|
24
|
+
{ role: 'user', content: prompt }
|
|
25
|
+
],
|
|
26
|
+
temperature: 0.7,
|
|
27
|
+
max_tokens: 4000,
|
|
28
|
+
stream: false
|
|
29
|
+
};
|
|
30
|
+
endpoint = this.isGroq ? '' : '/chat/completions';
|
|
31
|
+
if (this.config.apiKey) {
|
|
32
|
+
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Ollama API format
|
|
37
|
+
requestBody = {
|
|
38
|
+
model: this.config.model,
|
|
39
|
+
prompt: prompt,
|
|
40
|
+
system: systemPrompt,
|
|
41
|
+
stream: false,
|
|
42
|
+
options: {
|
|
43
|
+
temperature: 0.7,
|
|
44
|
+
top_p: 0.9,
|
|
45
|
+
top_k: 40,
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
endpoint = '/api/generate';
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(`${this.config.baseUrl}${endpoint}`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers,
|
|
54
|
+
body: JSON.stringify(requestBody),
|
|
55
|
+
signal: AbortSignal.timeout(this.config.timeout)
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new Error(`AI API error: ${response.status} ${response.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
return data;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (error instanceof Error) {
|
|
65
|
+
throw new Error(`Failed to connect to AI service: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
throw new Error('Unknown error occurred while connecting to AI service');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
extractResponseContent(data) {
|
|
71
|
+
if (this.isGroq) {
|
|
72
|
+
// Groq/OpenAI response format
|
|
73
|
+
return data.choices?.[0]?.message?.content || '';
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Ollama response format
|
|
77
|
+
return data.response || '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
buildUserProfileContext(context) {
|
|
81
|
+
const { userProfile, preferences, nutritionalTargets } = context;
|
|
82
|
+
let profileText = '';
|
|
83
|
+
if (userProfile.age)
|
|
84
|
+
profileText += `Age: ${userProfile.age} years old. `;
|
|
85
|
+
if (userProfile.gender)
|
|
86
|
+
profileText += `Gender: ${userProfile.gender}. `;
|
|
87
|
+
if (userProfile.height && userProfile.weight) {
|
|
88
|
+
profileText += `Height: ${userProfile.height}cm, Weight: ${userProfile.weight}kg. `;
|
|
89
|
+
}
|
|
90
|
+
if (userProfile.activityLevel)
|
|
91
|
+
profileText += `Activity level: ${userProfile.activityLevel}. `;
|
|
92
|
+
if (userProfile.healthGoals?.length) {
|
|
93
|
+
profileText += `Health goals: ${userProfile.healthGoals.join(', ')}. `;
|
|
94
|
+
}
|
|
95
|
+
if (userProfile.dietaryRestrictions?.length) {
|
|
96
|
+
profileText += `Dietary restrictions: ${userProfile.dietaryRestrictions.join(', ')}. `;
|
|
97
|
+
}
|
|
98
|
+
if (userProfile.allergies?.length) {
|
|
99
|
+
profileText += `Allergies: ${userProfile.allergies.join(', ')}. `;
|
|
100
|
+
}
|
|
101
|
+
if (userProfile.healthConditions?.length) {
|
|
102
|
+
profileText += `Health conditions: ${userProfile.healthConditions.join(', ')}. `;
|
|
103
|
+
}
|
|
104
|
+
if (preferences?.cuisineTypes?.length) {
|
|
105
|
+
profileText += `Preferred cuisines: ${preferences.cuisineTypes.join(', ')}. `;
|
|
106
|
+
}
|
|
107
|
+
if (preferences?.cookingTime) {
|
|
108
|
+
profileText += `Preferred cooking time: ${preferences.cookingTime} minutes max. `;
|
|
109
|
+
}
|
|
110
|
+
if (nutritionalTargets) {
|
|
111
|
+
profileText += `Nutritional targets: `;
|
|
112
|
+
if (nutritionalTargets.calories)
|
|
113
|
+
profileText += `${nutritionalTargets.calories} calories, `;
|
|
114
|
+
if (nutritionalTargets.protein)
|
|
115
|
+
profileText += `${nutritionalTargets.protein}g protein, `;
|
|
116
|
+
if (nutritionalTargets.carbs)
|
|
117
|
+
profileText += `${nutritionalTargets.carbs}g carbs, `;
|
|
118
|
+
if (nutritionalTargets.fat)
|
|
119
|
+
profileText += `${nutritionalTargets.fat}g fat. `;
|
|
120
|
+
}
|
|
121
|
+
return profileText.trim();
|
|
122
|
+
}
|
|
123
|
+
parseJSONResponse(response) {
|
|
124
|
+
try {
|
|
125
|
+
console.log('Raw AI response for debugging:', response);
|
|
126
|
+
// Clean up the response - remove any markdown code blocks and extra text
|
|
127
|
+
let cleanedResponse = response.trim();
|
|
128
|
+
// Remove markdown code blocks more aggressively
|
|
129
|
+
cleanedResponse = cleanedResponse.replace(/```json\s*/gi, '');
|
|
130
|
+
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);
|
|
137
|
+
}
|
|
138
|
+
console.log('Cleaned JSON response:', cleanedResponse);
|
|
139
|
+
// Try parsing the cleaned response
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(cleanedResponse);
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
catch (parseError) {
|
|
145
|
+
console.log('Direct parse failed, trying to fix common issues:', parseError);
|
|
146
|
+
// If direct parsing fails, try to fix common JSON issues
|
|
147
|
+
const fixedJson = this.fixCommonJSONIssues(cleanedResponse);
|
|
148
|
+
console.log('Fixed JSON:', fixedJson);
|
|
149
|
+
return JSON.parse(fixedJson);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
// Log the actual response for debugging
|
|
154
|
+
console.error('Failed to parse AI response. Raw response:', response);
|
|
155
|
+
console.error('Parse error:', error);
|
|
156
|
+
throw new Error(`Failed to parse AI response as JSON: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
fixCommonJSONIssues(jsonString) {
|
|
160
|
+
let fixed = jsonString;
|
|
161
|
+
// Fix common issues with AI-generated JSON
|
|
162
|
+
// 1. Fix trailing commas in arrays and objects
|
|
163
|
+
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, '\\"');
|
|
174
|
+
return fixed;
|
|
175
|
+
}
|
|
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
|
+
|
|
214
|
+
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`;
|
|
218
|
+
if (request.existingMeals?.length) {
|
|
219
|
+
prompt += `Avoid repeating these meals: ${request.existingMeals.join(', ')}\n\n`;
|
|
220
|
+
}
|
|
221
|
+
if (request.avoidIngredients?.length) {
|
|
222
|
+
prompt += `Avoid these ingredients: ${request.avoidIngredients.join(', ')}\n\n`;
|
|
223
|
+
}
|
|
224
|
+
if (request.focusAreas?.length) {
|
|
225
|
+
prompt += `Focus on these areas: ${request.focusAreas.join(', ')}\n\n`;
|
|
226
|
+
}
|
|
227
|
+
if (request.type === 'daily') {
|
|
228
|
+
prompt += `Generate 4 meals: breakfast, lunch, dinner, and one snack. `;
|
|
229
|
+
}
|
|
230
|
+
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.`;
|
|
234
|
+
try {
|
|
235
|
+
const response = await this.makeRequest(prompt, systemPrompt);
|
|
236
|
+
// Extract the actual response content
|
|
237
|
+
const responseContent = this.extractResponseContent(response);
|
|
238
|
+
// Log the raw response for debugging
|
|
239
|
+
console.log('Raw AI meal plan response:', responseContent);
|
|
240
|
+
const parsedData = this.parseJSONResponse(responseContent);
|
|
241
|
+
// Validate the parsed data has required fields
|
|
242
|
+
if (!parsedData.name || !parsedData.meals || !Array.isArray(parsedData.meals)) {
|
|
243
|
+
throw new Error('Invalid meal plan structure: missing required fields');
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
success: true,
|
|
247
|
+
data: parsedData,
|
|
248
|
+
usage: {
|
|
249
|
+
promptTokens: response.usage?.prompt_tokens || 0,
|
|
250
|
+
completionTokens: response.usage?.completion_tokens || 0,
|
|
251
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.error('Meal plan generation error:', error);
|
|
257
|
+
return {
|
|
258
|
+
success: false,
|
|
259
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
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
|
+
|
|
291
|
+
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`;
|
|
294
|
+
if (request.ingredients?.length) {
|
|
295
|
+
prompt += `Include these ingredients: ${request.ingredients.join(', ')}\n`;
|
|
296
|
+
}
|
|
297
|
+
if (request.cookingTime) {
|
|
298
|
+
prompt += `Maximum cooking time: ${request.cookingTime} minutes\n`;
|
|
299
|
+
}
|
|
300
|
+
if (request.difficulty) {
|
|
301
|
+
prompt += `Difficulty level: ${request.difficulty}\n`;
|
|
302
|
+
}
|
|
303
|
+
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.`;
|
|
307
|
+
try {
|
|
308
|
+
const response = await this.makeRequest(prompt, systemPrompt);
|
|
309
|
+
// Extract the actual response content
|
|
310
|
+
const responseContent = this.extractResponseContent(response);
|
|
311
|
+
// Log the raw response for debugging
|
|
312
|
+
console.log('Raw AI recipe response:', responseContent);
|
|
313
|
+
const parsedData = this.parseJSONResponse(responseContent);
|
|
314
|
+
// Validate the parsed data has required fields
|
|
315
|
+
if (!parsedData.name || !parsedData.ingredients || !parsedData.instructions) {
|
|
316
|
+
throw new Error('Invalid recipe structure: missing required fields');
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
success: true,
|
|
320
|
+
data: parsedData,
|
|
321
|
+
usage: {
|
|
322
|
+
promptTokens: response.usage?.prompt_tokens || 0,
|
|
323
|
+
completionTokens: response.usage?.completion_tokens || 0,
|
|
324
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error('Recipe generation error:', error);
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
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
|
+
}
|
|
355
|
+
]`;
|
|
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
|
|
371
|
+
4. Consider the user's dietary restrictions and preferences`;
|
|
372
|
+
try {
|
|
373
|
+
const response = await this.makeRequest(prompt, systemPrompt);
|
|
374
|
+
const responseContent = this.extractResponseContent(response);
|
|
375
|
+
const parsedData = this.parseJSONResponse(responseContent);
|
|
376
|
+
return {
|
|
377
|
+
success: true,
|
|
378
|
+
data: parsedData,
|
|
379
|
+
usage: {
|
|
380
|
+
promptTokens: response.usage?.prompt_tokens || 0,
|
|
381
|
+
completionTokens: response.usage?.completion_tokens || 0,
|
|
382
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
return {
|
|
388
|
+
success: false,
|
|
389
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
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"]
|
|
401
|
+
}`;
|
|
402
|
+
const userContext = this.buildUserProfileContext(context);
|
|
403
|
+
const totalNutrition = meals.reduce((total, meal) => ({
|
|
404
|
+
calories: total.calories + meal.nutrition.calories,
|
|
405
|
+
protein: total.protein + meal.nutrition.protein,
|
|
406
|
+
carbs: total.carbs + meal.nutrition.carbs,
|
|
407
|
+
fat: total.fat + meal.nutrition.fat
|
|
408
|
+
}), { 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
|
|
425
|
+
3. Concrete improvements that could be made`;
|
|
426
|
+
try {
|
|
427
|
+
const response = await this.makeRequest(prompt, systemPrompt);
|
|
428
|
+
const responseContent = this.extractResponseContent(response);
|
|
429
|
+
const parsedData = this.parseJSONResponse(responseContent);
|
|
430
|
+
return {
|
|
431
|
+
success: true,
|
|
432
|
+
data: parsedData,
|
|
433
|
+
usage: {
|
|
434
|
+
promptTokens: response.usage?.prompt_tokens || 0,
|
|
435
|
+
completionTokens: response.usage?.completion_tokens || 0,
|
|
436
|
+
totalTokens: response.usage?.total_tokens || 0
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
return {
|
|
442
|
+
success: false,
|
|
443
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
exports.UnifiedAIService = UnifiedAIService;
|
package/dist/src/types/ai.d.ts
CHANGED
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.17",
|
|
4
4
|
"main": "dist/src/index.js",
|
|
5
5
|
"types": "dist/src/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -60,6 +60,10 @@
|
|
|
60
60
|
"types": "./dist/src/services/aiMealPlanGenerator.d.ts",
|
|
61
61
|
"default": "./dist/src/services/aiMealPlanGenerator.js"
|
|
62
62
|
},
|
|
63
|
+
"./services/unified": {
|
|
64
|
+
"types": "./dist/src/services/unifiedAIService.d.ts",
|
|
65
|
+
"default": "./dist/src/services/unifiedAIService.js"
|
|
66
|
+
},
|
|
63
67
|
"./services/shoppingListGenerator": {
|
|
64
68
|
"types": "./dist/src/services/shoppingListGenerator.d.ts",
|
|
65
69
|
"default": "./dist/src/services/shoppingListGenerator.js"
|