first commit

This commit is contained in:
Dhananjaya99
2026-09-28 11:44:26 +05:30
parent 721743ae6b
commit bffc9b1686
424 changed files with 46474 additions and 0 deletions
+68
View File
@@ -0,0 +1,68 @@
using System.Text.Json;
namespace Fitness_core_BE.AI
{
public class GeminiClient : IAiClient
{
private readonly HttpClient _http;
private readonly IConfiguration _config;
public GeminiClient(HttpClient http, IConfiguration configuration)
{
_http = http;
_config = configuration;
}
public async Task<string> GenerateJsonAsync( string systemPrompt, object payload)
{
string apiKey = _config["Gemini:ApiKey"];
if (string.IsNullOrEmpty(apiKey))
throw new Exception("Gemini API key is not configured.");
var requestBody = new
{
contents = new[]
{
new
{
parts = new[]
{
new { text = systemPrompt },
new { text = JsonSerializer.Serialize(payload) }
}
}
},
generationConfig = new
{
temperature = 0.4,
maxOutputTokens = 10000
}
};
var response = await _http.PostAsJsonAsync(
$"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key={apiKey}",
requestBody
);
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadAsStringAsync();
throw new Exception($"Gemini API Error: {error}");
}
var jsonString = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(jsonString);
var text = doc.RootElement
.GetProperty("candidates")[0]
.GetProperty("content")
.GetProperty("parts")[0]
.GetProperty("text")
.GetString();
return text ?? throw new Exception("No response text from Gemini.");
}
}
}
+7
View File
@@ -0,0 +1,7 @@
namespace Fitness_core_BE.AI
{
public interface IAiClient
{
Task<string> GenerateJsonAsync(string systemPrompt, object payload);
}
}
+213
View File
@@ -0,0 +1,213 @@
using Fitness_core_BE.Helpers;
using System.Text.Json;
namespace Fitness_core_BE.AI
{
public class MealPlanOrchestrator
{
private readonly IAiClient _aiClient;
private readonly ModificationGuard _guard;
public MealPlanOrchestrator( IAiClient aiClient, ModificationGuard guard)
{
_aiClient = aiClient;
_guard = guard;
}
public async Task<object> GeneratePlanAsync(object payload)
{
var data = DeserializePayload.Deserialize(payload);
decimal bmi = DataExtractor.GetRequiredDecimal(data, "bmi");
string country = DataExtractor.GetRequiredString(data, "country");
string province = DataExtractor.GetRequiredString(data, "province");
string goal = DataExtractor.GetRequiredString(data, "goal");
decimal budget = DataExtractor.GetRequiredDecimal(data, "budgetPerWeek");
int mealsPerDay = DataExtractor.GetRequiredInt(data, "mealsPerDay");
bool isVegetarian = DataExtractor.GetRequiredBool(data, "isVegetarian");
// Basic validation
if (bmi < 10 || bmi > 60)
throw new Exception("Invalid BMI");
var systemPrompt = @"
You are a certified nutritionist.
Your response must be STRICTLY a JSON object.
Output format rules:
1. The first character of your response MUST be {
2. The last character MUST be }
3. Do not include ``` or any markdown formatting
4. Do not include any explanation text
5. Do not include comments
6. Do not prefix with ""Here is""
7. Do not suffix with any text
Return ONLY valid JSON.
Structure:
{
name: string,
description: string,
mealItems: [
{
name: string,
description: string,
calories: number,
protein: number,
carbohydrates: number,
fats: number,
servingSize: string,
mealItems: [
{ food: string, quantity: string }
],
preparationNotes: string
}
]
}
";
var jsonResponse = await _aiClient.GenerateJsonAsync(
systemPrompt,
data
);
// Validate JSON before returning
try
{
var parsed = JsonSerializer.Deserialize<object>(jsonResponse);
if (parsed == null)
throw new Exception("Invalid AI JSON");
}
catch
{
throw new Exception("AI returned invalid JSON structure");
}
return JsonSerializer.Deserialize<object>(jsonResponse)!;
}
public async Task<object> UpdatePlanAsync(object payload)
{
var data = DeserializePayload.Deserialize(payload);
if (!data.ContainsKey("currentPlan"))
throw new Exception("Current plan missing");
string modification =
DataExtractor.GetRequiredString(data, "modification");
_guard.Validate(modification);
var systemPrompt = @"
You are a professional nutrition AI.
You will receive:
1) The current meal plan in valid JSON format.
2) A user modification request in free text.
Your task:
- Carefully analyze the modification request.
- Modify ONLY the specific elements explicitly requested by the user.
- Do NOT change anything else.
VERY IMPORTANT RULES:
- Do NOT change the plan name unless the user explicitly asks.
- Do NOT change the number of meals unless requested.
- Do NOT modify calories, protein, carbs, fats unless the request requires it.
- Do NOT rewrite descriptions unless necessary for the modification.
- Do NOT change serving sizes unless required.
- Do NOT add or remove meals unless explicitly requested.
- Do NOT modify ingredients that are unrelated to the request.
- Preserve every unchanged value exactly as provided.
Scope control:
If the request only affects one ingredient, update only that ingredient.
If the request does not affect the plan, return it unchanged.
Preserve the exact internal structure of the meal plan.
Do NOT rename fields.
Do NOT remove required properties.
Do NOT change the JSON schema.
Return your response strictly in this JSON format:
{
""reason"": string,
""modified"": boolean,
""currentPlan"": {
""name"": string,
""description"": string,
""mealItems"": [
{
""name"": string,
""description"": string,
""calories"": number,
""protein"": number,
""carbohydrates"": number,
""fats"": number,
""servingSize"": string,
""mealItems"": [
{ ""food"": string, ""quantity"": string }
],
""preparationNotes"": string
}
]
},
""updatedPlan"": {
""name"": string,
""description"": string,
""mealItems"": [
{
""name"": string,
""description"": string,
""calories"": number,
""protein"": number,
""carbohydrates"": number,
""fats"": number,
""servingSize"": string,
""mealItems"": [
{ ""food"": string, ""quantity"": string }
],
""preparationNotes"": string
}
]
}
}
STRICT OUTPUT RULES:
- Return ONLY valid raw JSON.
- No markdown.
- No explanations.
- First character must be {.
- Last character must be }.
- No trailing commas.
- No extra commentary.
If modification cannot be applied:
- Set ""modified"": false
- Keep currentPlan and updatedPlan identical.
- Explain reason clearly.
";
var jsonResponse = await _aiClient.GenerateJsonAsync(
systemPrompt,
data
);
try
{
var parsed = JsonSerializer.Deserialize<object>(jsonResponse);
if (parsed == null)
throw new Exception("Invalid JSON");
}
catch
{
throw new Exception("AI returned invalid JSON");
}
return JsonSerializer.Deserialize<object>(jsonResponse)!;
}
}
}
+47
View File
@@ -0,0 +1,47 @@
namespace Fitness_core_BE.AI
{
public class ModificationGuard
{
private static readonly string[] AllowedKeywords =
{
"replace",
"remove",
"allergy",
"change",
"vegetarian",
"calorie",
"protein",
"carb",
"fat",
"meal",
"ingredient"
};
private static readonly string[] BlockedPatterns =
{
"ignore previous instructions",
"system prompt",
"act as",
"bypass",
"developer mode",
"jailbreak"
};
public void Validate(string message)
{
if (string.IsNullOrWhiteSpace(message))
throw new Exception("Modification cannot be empty.");
if (message.Length > 500)
throw new Exception("Modification too long.");
var lower = message.ToLower();
if (BlockedPatterns.Any(p => lower.Contains(p)))
throw new Exception("Invalid modification request.");
//if (!AllowedKeywords.Any(k => lower.Contains(k)))
// throw new Exception("Only meal plan modifications are allowed.");
}
}
}
+229
View File
@@ -0,0 +1,229 @@
using Fitness_core_BE.Helpers;
using System.Text.Json;
namespace Fitness_core_BE.AI
{
public class WorkoutPlanOrchestrator
{
private readonly IAiClient _aiClient;
private readonly ModificationGuard _guard;
public WorkoutPlanOrchestrator(IAiClient aiClient, ModificationGuard guard)
{
_aiClient = aiClient;
_guard = guard;
}
public async Task<object> GeneratePlanAsync(object payload)
{
var data = DeserializePayload.Deserialize(payload);
string fitnessLevel = DataExtractor.GetRequiredString(data, "fitnessLevel");
string goal = DataExtractor.GetRequiredString(data, "goal");
int daysPerWeek = DataExtractor.GetRequiredInt(data, "daysPerWeek");
string availableEquipment = DataExtractor.GetRequiredString(data, "availableEquipment");
int durationMinutes = DataExtractor.GetRequiredInt(data, "durationPerSession");
// Optional fields
string? injuryOrLimitations = data.ContainsKey("injuryOrLimitations")
? data["injuryOrLimitations"].GetString()
: null;
// Basic validation
if (daysPerWeek < 1 || daysPerWeek > 7)
throw new Exception("Days per week must be between 1 and 7");
if (durationMinutes < 15 || durationMinutes > 180)
throw new Exception("Duration must be between 15 and 180 minutes");
//bmi, body shape , target , should check with tartget and body shape matching, time peiod, Continuose-week days,,, whether going to Gym or not, Injuries, any seriose conditions,
var systemPrompt = @"
You are a certified personal trainer and fitness expert.
Your response must be STRICTLY a JSON object.
Output format rules:
1. The first character of your response MUST be {
2. The last character MUST be }
3. Do not include ``` or any markdown formatting
4. Do not include any explanation text
5. Do not include comments
6. Do not prefix with ""Here is""
7. Do not suffix with any text
Return ONLY valid JSON.
Structure:
{
""name"": string,
""description"": string,
""workoutItems"": [
{
""exerciseDay"": string,
""description"": string,
""exercises"": {
""Exercise Name"": ""sets x reps or duration""
},
""duration"": string (total session time including warmup and cooldown, with units),
""wormUp"": string (warmup instructions with an explicit time and unit),
""cooldown"": string (cooldown instructions with an explicit time and unit),
""howToDo"": string (JSON string with rest periods and notes),
""doURL"": string (exercise reference URL)
}
]
}
Important guidelines:
- Create a workout plan based on the user's fitness level, goal, and available equipment
- Include appropriate exercises for the specified number of days per week
- Each workout must fit within the specified duration
- Set ""duration"" to the requested total duration per session (for example, ""45 minutes"")
- ""wormUp"" and ""cooldown"" must each include an explicit duration such as ""5 minutes""
- The main workout time plus wormUp time plus cooldown time must equal the total ""duration""
- The ""exercises"" field should be an object with exercise names as keys and sets/reps as values
- The ""howToDo"" field should be a JSON string containing warmup, rest periods, and notes
- Consider any injury or limitations mentioned
- Provide realistic sets, reps, and rest periods based on fitness level
- Include proper progression and variety
";
var jsonResponse = await _aiClient.GenerateJsonAsync(
systemPrompt,
data
);
// Validate JSON before returning
try
{
var parsed = JsonSerializer.Deserialize<object>(jsonResponse);
if (parsed == null)
throw new Exception("Invalid AI JSON");
}
catch
{
throw new Exception("AI returned invalid JSON structure");
}
return JsonSerializer.Deserialize<object>(jsonResponse)!;
}
public async Task<object> UpdatePlanAsync(object payload)
{
var data = DeserializePayload.Deserialize(payload);
if (!data.ContainsKey("currentWorkoutPlan"))
throw new Exception("Current plan missing");
string modification = DataExtractor.GetRequiredString(data, "modification");
_guard.Validate(modification);
var systemPrompt = @"
You are a professional fitness AI.
You will receive:
1) The current workout plan in valid JSON format.
2) A user modification request in free text.
3) BMI, body shape, target, time period, continuous workout days, whether going to gym or not, injuries, any seriose conditions.
Your task:
- Carefully analyze the modification request.
- Modify ONLY the specific elements explicitly requested by the user.
- Do NOT change anything else.
VERY IMPORTANT RULES:
- Do NOT change the plan name unless the user explicitly asks.
- Do NOT change the number of workout days unless requested.
- Do NOT modify exercises that are not mentioned in the request.
- Do NOT rewrite descriptions unless necessary for the modification.
- Do NOT change sets, reps, or rest periods unless the request requires it.
- Do NOT add or remove exercises unless explicitly requested.
- Do NOT modify workout structure that is unrelated to the request.
- Preserve every unchanged value exactly as provided.
- Analyze the user's BMI, body shape, target, time period, continuous workout days, gym access, injuries, and any seriose conditions.
- Stricly focus if mentioned any seriose health conditions or injuries and do not suggest any exercise that can cause harm to the user.
- If the modification request is not logical or contradicts fitness principles, do not apply it and explain why in the reason field.
Scope control:
If the request only affects one exercise, update only that exercise.
If the request does not affect the plan, return it unchanged.
Preserve the exact internal structure of the workout plan.
Do NOT rename fields.
Do NOT remove required properties.
Do NOT change the JSON schema.
Return your response strictly in this JSON format:
{
""reason"": string,
""modified"": boolean,
""currentPlan"": {
""name"": string,
""description"": string,
""workoutItems"": [
{
""exerciseDay"": string,
""description"": string,
""exercises"": {
""Exercise Name"": ""sets x reps or duration""
},
""howToDo"": string,
""doURL"": string
}
]
},
""updatedPlan"": {
""name"": string,
""description"": string,
""workoutItems"": [
{
""exerciseDay"": string,
""description"": string,
""exercises"": {
""Exercise Name"": ""sets x reps or duration""
},
""howToDo"": string,
""doURL"": string
}
]
}
}
STRICT OUTPUT RULES:
- Return ONLY valid raw JSON.
- No markdown.
- No explanations.
- First character must be {.
- Last character must be }.
- No trailing commas.
- No extra commentary.
If modification cannot be applied:
- Set ""modified"": false
- Keep currentPlan and updatedPlan identical.
- Explain reason clearly.
";
var jsonResponse = await _aiClient.GenerateJsonAsync(
systemPrompt,
data
);
try
{
var parsed = JsonSerializer.Deserialize<object>(jsonResponse);
if (parsed == null)
throw new Exception("Invalid JSON");
}
catch
{
throw new Exception("AI returned invalid JSON");
}
return JsonSerializer.Deserialize<object>(jsonResponse)!;
}
}
}