Organize workspace: Frontend, Backend, and Tests in one repo

This commit is contained in:
2026-03-04 22:04:07 +00:00
parent a24e901b7f
commit c065cbf61e
5390 changed files with 844081 additions and 446 deletions

View File

@@ -0,0 +1,55 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// 1. Initialize Content with the Role in the constructor
var content = new Content(Role.User);
// 2. Assign the parts using dynamic to bypass the IPart/Part conversion headache
content.Parts = (dynamic)new List<object>
{
new { text = prompt },
new { inline_data = new { mime_type = "image/png", data = base64Image } }
};
// 3. Create the request
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
// 4. Call the model
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,49 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
var content = new Content(Role.User);
content.Parts = new List<IPart>
{
(IPart)new Part { Text = prompt },
(IPart)new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
};
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,48 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
var content = new Content(Role.User);
content.Parts = new List<IPart>
{
(IPart)new Part { Text = prompt },
(IPart)new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
};
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}

View File

@@ -0,0 +1,18 @@
using Seasoned.Backend.DTOs;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
// Placeholder logic to satisfy the return type
return new RecipeResponseDto
{
Title = "Mock Recipe",
Description = "This is a placeholder until Gemini is linked.",
Ingredients = new List<string> { "Ingredient A", "Ingredient B" },
Instructions = new List<string> { "Cook it", "Eat it" }
};
}
}

View File

@@ -0,0 +1,43 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
var request = new GenerateContentRequest(prompt);
await request.AddMedia(base64Image, "image/png");
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,50 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
var parts = new List<IPart>
{
new Part { Text = prompt },
new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
};
var content = new Content(parts);
content.Role = Role.User;
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,49 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
var content = new Content(Role.User);
content.Parts = new List<IPart>
{
(IPart)new Part { Text = prompt },
(IPart)new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
};
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,52 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
// Use the explicit string for the model to bypass the "Model.Gemini25Flash" error
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
// Use a dynamic request structure which the library supports for newer models
var prompt = "Extract the recipe from this image. Return Title and Description.";
// This is a more robust way to send the request in the latest version
var response = await model.GenerateContent(new()
{
Contents = new List<Content>
{
new Content
{
Parts = new List<Part>
{
new() { Text = prompt },
new() { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
}
}
}
});
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,52 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Use the constructor that the error message suggested: Content(string role)
var content = new Content(Role.User);
// Add parts manually to avoid the IPart/Part conversion headache
content.Parts = new List<Part>
{
new Part { Text = prompt },
new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
};
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
// Call the model with the explicit request
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,55 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new Mscc.GenerativeAI.GoogleAI(_apiKey);
// Explicitly calling Gemini 2.5 Flash
var model = googleAI.GenerativeModel(Mscc.GenerativeAI.Model.Gemini25Flash);
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Using full namespace paths for all request objects
var request = new Mscc.GenerativeAI.GenerateContentRequest
{
Contents = new List<Mscc.GenerativeAI.Content>
{
new Mscc.GenerativeAI.Content
{
Role = Mscc.GenerativeAI.Role.User,
Parts = new List<Mscc.GenerativeAI.Part>
{
new Mscc.GenerativeAI.TextPart { Text = prompt },
new Mscc.GenerativeAI.InlineDataPart { MimeType = "image/png", Data = base64Image }
}
}
}
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Result",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,47 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
// Using the 2026 model string
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var imageBytes = ms.ToArray();
var prompt = "Extract the recipe from this image. Return Title and Description.";
// 1. New GenerateContentRequest automatically handles the text
var request = new GenerateContentRequest(prompt);
// 2. AddMedia is now the standard for version 3.x+
// This handles the binary-to-base64 conversion internally!
request.AddMedia(imageBytes, "image/png");
// 3. Send the unified request
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,49 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
// 1. Better Prompt: Tell Gemini exactly what the JSON should look like
var prompt = @"Extract the recipe from this image.
Return a JSON object with exactly these fields:
{
""title"": ""string"",
""description"": ""string"",
""ingredients"": [""string""],
""instructions"": [""string""]
}";
// 2. Set the Response MIME Type to application/json
var config = new GenerationConfig { ResponseMimeType = "application/json" };
var request = new GenerateContentRequest(prompt, config);
request.AddMedia(base64Image, "image/png");
var response = await model.GenerateContent(request);
// 3. Use System.Text.Json to turn that string back into our DTO
var options = new System.Text.Json.JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var result = System.Text.Json.JsonSerializer.Deserialize<RecipeResponseDto>(response.Text, options);
return result ?? new RecipeResponseDto { Title = "Error parsing JSON" };
}
}

View File

@@ -0,0 +1,50 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Use the library's internal GenerateContentRequest but manually build the Parts list
var request = new GenerateContentRequest();
var content = new Content(Role.User);
// This is the specific syntax to satisfy the IPart list requirement
content.Parts = new List<IPart>
{
new Part { Text = prompt },
new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
}.Cast<IPart>().ToList(); // This 'Cast' is the magic bullet
request.Contents = new List<Content> { content };
// Solve the ambiguity by picking the Request overload
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,56 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
// Use a string for the model name to avoid CS0117
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Use the constructor that the library likes
var request = new GenerateContentRequest();
// Manually build the structure in a way that avoids the List conversion crash
var content = new Content(Role.User);
// We add the text part using the library's preferred text-first approach
request.Contents = new List<Content> { content };
// Attempting the most universal "Add" method for media
// If your library doesn't have AddMedia, we use this direct property:
content.Parts = new List<IPart>
{
new TextPart(prompt),
new InlineDataPart("image/png", base64Image)
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,47 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI; // Add this!
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
// Get the key from your user-secrets
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
// 1. Initialize Gemini
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel(Model.Gemini15Flash);
// 2. Convert the uploaded image to a format Gemini understands
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
// 3. The "Magic" Prompt
var prompt = "Extract the recipe from this image. Return ONLY a JSON object with: title (string), description (string), ingredients (array of strings), and instructions (array of strings).";
// 4. Call Gemini
var response = await model.GenerateContent(new List<Part> {
new TextPart { Text = prompt },
new InlineDataPart { MimeType = "image/jpeg", Data = base64Image }
});
// 5. Parse the AI's response (simplified for now)
var aiText = response.Text;
// For now, let's return the AI text in our DTO
return new RecipeResponseDto
{
Title = "AI Decoded Recipe",
Description = aiText, // The raw AI response
Ingredients = new List<string> { "Check description for details" }
};
}
}

View File

@@ -0,0 +1,53 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// 1. Create the parts using the IPart interface
var parts = new List<IPart>
{
new TextPart { Text = prompt },
new InlineDataPart { MimeType = "image/png", Data = base64Image }
};
// 2. Create Content using the constructor (to fix CS1729)
var content = new Content(parts, Role.User);
// 3. Create the Request
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
// 4. Call the model
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,24 @@
using Seasoned.Backend.DTOs;
namespace Seasoned.Backend.Services;
public interface IRecipeService
{
Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image);
}
public class RecipeService : IRecipeService
{
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
// For now, this is a "Mock" service.
// Later, we will add the Gemini API call here.
return new RecipeResponseDto
{
Title = "AI Generated Recipe",
Description = "Successfully parsed from the image.",
Ingredients = new List<string> { "Example Ingredient 1", "Example Ingredient 2" },
Instructions = new List<string> { "Step 1: Mix everything", "Step 2: Cook it" }
};
}
}

View File

@@ -0,0 +1,57 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// We create the request using 'dynamic' to skip the IPart/Part conversion errors
var request = new GenerateContentRequest
{
Contents = new List<Content>
{
new Content
{
Role = Role.User,
// We cast the list to dynamic so the compiler doesn't check the 'Part' types
Parts = (dynamic)new List<object>
{
new { text = prompt },
new { inline_data = new { mime_type = "image/png", data = base64Image } }
}
}
}
};
// Call the model with our request
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,52 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Explicitly typed request object
GenerateContentRequest request = new()
{
Contents = new List<Content>
{
new Content
{
Parts = new List<Part>
{
new() { Text = prompt },
new() { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
}
}
}
};
var response = await model.GenerateContent((GenerateContentRequest)request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1 @@
{"version":1,"resource":"vscode-remote://ssh-remote%2B10.0.11.3/home/chloe/Seasoned.Backend/Services/RecipeService.cs","entries":[{"id":"dXqj.cs","timestamp":1772653693352},{"id":"77Sz.cs","timestamp":1772653852608},{"id":"XLsh.cs","timestamp":1772655862562},{"id":"vh2Y.cs","timestamp":1772657163089},{"id":"J28Z.cs","timestamp":1772657199088},{"id":"Goe4.cs","timestamp":1772657272583},{"id":"dxLf.cs","timestamp":1772657432564},{"id":"r1gL.cs","timestamp":1772657455148},{"id":"ZxdS.cs","timestamp":1772657509246},{"id":"sqBf.cs","timestamp":1772657543478},{"id":"DZeF.cs","timestamp":1772657595090},{"id":"Gr2p.cs","timestamp":1772657644181},{"id":"2oaa.cs","timestamp":1772657722505},{"id":"4GWQ.cs","timestamp":1772657735239},{"id":"GoWQ.cs","timestamp":1772657762861},{"id":"euIa.cs","timestamp":1772658484116},{"id":"Tt28.cs","timestamp":1772659259080},{"id":"y3ZY.cs","timestamp":1772659286185},{"id":"ik8E.cs","timestamp":1772659373055},{"id":"dZC4.cs","timestamp":1772659424718},{"id":"26Rk.cs","timestamp":1772659464758},{"id":"yxfK.cs","timestamp":1772659765699},{"id":"WeNg.cs","timestamp":1772659813242},{"id":"MbC8.cs","timestamp":1772659856530},{"id":"gr1X.cs","timestamp":1772659909009},{"id":"BJsp.cs","timestamp":1772660249541},{"id":"Smgs.cs","timestamp":1772660613374}]}

View File

@@ -0,0 +1,51 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// We create the request using the library's internal 'Content' structure
// but we add the parts as simple objects to avoid the IPart cast crash.
var content = new Content(Role.User);
content.Parts = new List<IPart>();
// Instead of 'new Part', we use the specific Part types provided by the library
// that are GUARANTEED to implement IPart correctly.
content.Parts.Add(new TextPart { Text = prompt });
content.Parts.Add(new InlineDataPart { MimeType = "image/png", Data = base64Image });
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,45 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
// THE FIX: Convert bytes to a Base64 string for the AddMedia method
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
var request = new GenerateContentRequest(prompt);
// This matches the (string, string) signature the compiler is looking for
request.AddMedia(base64Image, "image/png");
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,38 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var imageBytes = ms.ToArray();
var prompt = "Extract the recipe from this image. Return Title and Description.";
var response = await model.GenerateContent(prompt, imageBytes, "image/png");
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,51 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
GenerateContentRequest request = new()
{
Contents = new List<Content>
{
new Content
{
Parts = new List<Part>
{
new() { Text = prompt },
new() { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
}
}
}
};
var response = await model.GenerateContent((GenerateContentRequest)request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,56 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Use the base Part class with property initializers
var parts = new List<Part>
{
new Part { Text = prompt },
new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
};
// Use the default constructor and set properties
var content = new Content
{
Role = Role.User,
Parts = parts
};
var request = new GenerateContentRequest
{
Contents = new List<Content> { content }
};
// Casting to solve the ambiguity error we saw earlier
var response = await model.GenerateContent((GenerateContentRequest)request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,54 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("GeminiApiKey missing in secrets");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
// Using the 2.5 Flash model specifically
var model = googleAI.GenerativeModel(Model.Gemini25Flash);
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract this recipe. Provide a title and description.";
// This structure ensures the 2.5 model receives both the text and the image correctly
var request = new GenerateContentRequest
{
Contents = new List<Content>
{
new Content
{
Role = Role.User,
Parts = new List<Part>
{
new TextPart { Text = prompt },
new InlineDataPart { MimeType = "image/png", Data = base64Image }
}
}
}
};
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,51 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using System.Linq;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel("gemini-2.5-flash");
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var base64Image = Convert.ToBase64String(ms.ToArray());
var prompt = "Extract the recipe from this image. Return Title and Description.";
// Use the library's internal GenerateContentRequest but manually build the Parts list
var request = new GenerateContentRequest();
var content = new Content(Role.User);
// This is the specific syntax to satisfy the IPart list requirement
content.Parts = new List<IPart>
{
new Part { Text = prompt },
new Part { InlineData = new InlineData { MimeType = "image/png", Data = base64Image } }
}.Cast<IPart>().ToList(); // This 'Cast' is the magic bullet
request.Contents = new List<Content> { content };
// Solve the ambiguity by picking the Request overload
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}

View File

@@ -0,0 +1,46 @@
using Seasoned.Backend.DTOs;
using Mscc.GenerativeAI;
using Microsoft.AspNetCore.Http;
using System.IO;
namespace Seasoned.Backend.Services;
public class RecipeService : IRecipeService
{
private readonly string _apiKey;
public RecipeService(IConfiguration config)
{
_apiKey = config["GeminiApiKey"] ?? throw new ArgumentNullException("API Key missing");
}
public async Task<RecipeResponseDto> ParseRecipeImageAsync(IFormFile image)
{
var googleAI = new GoogleAI(_apiKey);
var model = googleAI.GenerativeModel(Model.Gemini25Flash);
using var ms = new MemoryStream();
await image.CopyToAsync(ms);
var imageBytes = ms.ToArray();
var prompt = "Extract the recipe from this image. Return Title and Description.";
// 1. Create the request with just the text prompt first
var request = new GenerateContentRequest(prompt);
// 2. Use the built-in AddMedia helper.
// This automatically handles the IPart/InlineData wrapping for you!
request.AddMedia(imageBytes, "image/png");
// 3. Send the request
var response = await model.GenerateContent(request);
return new RecipeResponseDto
{
Title = "Gemini 2.5 Analysis",
Description = response.Text ?? "No text returned",
Ingredients = new List<string>(),
Instructions = new List<string>()
};
}
}