diff --git a/Examples/Examples.SimpleConsole/ImageArgs.cs b/Examples/Examples.SimpleConsole/ImageArgs.cs new file mode 100644 index 00000000..5e44210f --- /dev/null +++ b/Examples/Examples.SimpleConsole/ImageArgs.cs @@ -0,0 +1,14 @@ +namespace Examples.SimpleConsole; + + +public class GenerateImageArgs +{ + public string Prompt { get; set; } = string.Empty; +} + +public class EditImageArgs +{ + public string ImageUrl { get; set; } = string.Empty; + public string Prompt { get; set; } = string.Empty; +} + diff --git a/Examples/Examples.SimpleConsole/ImageTools.cs b/Examples/Examples.SimpleConsole/ImageTools.cs new file mode 100644 index 00000000..37fba557 --- /dev/null +++ b/Examples/Examples.SimpleConsole/ImageTools.cs @@ -0,0 +1,177 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Examples.SimpleConsole; + +namespace MaIN.NET.Examples.TogetherAI; + +public static class ImageTools +{ + private const string TogetherApiUrl = "https://api.together.xyz/v1/images/generations"; + private const string ImgbbApiUrl = "https://api.imgbb.com/1/upload"; + private const string Model = "black-forest-labs/FLUX.1-schnell"; + private static readonly HttpClient HttpClient = new(); + private static string? TogetherApiKey => "<>"; + private static string? ImgbbApiKey => "<>"; + + public static async Task GenerateImage(GenerateImageArgs args) + { + try + { + if (string.IsNullOrEmpty(TogetherApiKey)) + return new { error = "Set TOGETHER_API_KEY environment variable" }; + + if (string.IsNullOrEmpty(ImgbbApiKey)) + return new { error = "Set IMGBB_API_KEY environment variable" }; + + // Generate image with Together AI + var requestBody = new + { + model = Model, + prompt = args.Prompt, + width = 1024, + height = 768, + steps = 10, + n = 1, + response_format = "b64_json" + }; + + var json = JsonSerializer.Serialize(requestBody); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + using var request = new HttpRequestMessage(HttpMethod.Post, TogetherApiUrl); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TogetherApiKey); + request.Content = content; + + var response = await HttpClient.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + return new { error = $"Together API error: {response.StatusCode}", details = responseContent }; + + var result = JsonSerializer.Deserialize(responseContent); + if (result?.Data == null || result.Data.Length == 0) + return new { error = "No image generated" }; + + var imageBase64 = result.Data[0].B64Json; + + // Upload to imgbb + var imgbbUrl = await UploadToImgbb(imageBase64); + if (imgbbUrl == null) + return new { error = "Failed to upload to imgbb" }; + + return new + { + success = true, + url = imgbbUrl + }; + } + catch (Exception ex) + { + return new { error = ex.Message }; + } + } + + public static async Task EditImage(EditImageArgs args) + { + try + { + if (string.IsNullOrEmpty(TogetherApiKey)) + return new { error = "Set TOGETHER_API_KEY environment variable" }; + + if (string.IsNullOrEmpty(ImgbbApiKey)) + return new { error = "Set IMGBB_API_KEY environment variable" }; + + if (string.IsNullOrEmpty(args.ImageUrl)) + return new { error = "Provide ImageUrl" }; + + + var requestBody = new + { + model = "black-forest-labs/FLUX.1-kontext-dev", + prompt = "make this dragon with cat head", + image_url = "https://ik.imagekit.io/g3m8563jj/together-image.png?updatedAt=1762246376998", + width = 1024, + height = 768, + //response_format = "b64_json" + }; + + var json = JsonSerializer.Serialize(requestBody); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + using var request = new HttpRequestMessage(HttpMethod.Post, TogetherApiUrl); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", TogetherApiKey); + request.Content = content; + + var response = await HttpClient.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + return new { error = $"Together API error: {response.StatusCode}", details = responseContent }; + + var result = JsonSerializer.Deserialize(responseContent); + if (result?.Data == null || result.Data.Length == 0) + return new { error = "No edited image returned" }; + + var editedImageBase64 = result.Data[0].B64Json; + + var imgbbUrl = await UploadToImgbb(editedImageBase64); + if (imgbbUrl == null) + return new { error = "Failed to upload to imgbb" }; + + return new + { + success = true, + url = imgbbUrl + }; + } + catch (Exception ex) + { + return new { error = ex.Message }; + } + } + + private static async Task UploadToImgbb(string base64Image) + { + try + { + var formData = new MultipartFormDataContent(); + formData.Add(new StringContent(ImgbbApiKey!), "key"); + formData.Add(new StringContent(base64Image), "image"); + + var response = await HttpClient.PostAsync(ImgbbApiUrl, formData); + var responseContent = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + return null; + + var result = JsonSerializer.Deserialize(responseContent); + return result?.Data?.Url; + } + catch + { + return null; + } + } +} + +public class TogetherImageResponse +{ + [JsonPropertyName("data")] public TogetherImageData[]? Data { get; set; } +} + +public class TogetherImageData +{ + [JsonPropertyName("b64_json")] public string B64Json { get; set; } = string.Empty; +} + +public class ImgbbResponse +{ + [JsonPropertyName("data")] public ImgbbData? Data { get; set; } +} + +public class ImgbbData +{ + [JsonPropertyName("url")] public string Url { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Examples/Examples.SimpleConsole/Program.cs b/Examples/Examples.SimpleConsole/Program.cs index 3ace6527..fbb83689 100644 --- a/Examples/Examples.SimpleConsole/Program.cs +++ b/Examples/Examples.SimpleConsole/Program.cs @@ -1,14 +1,70 @@ -using MaIN.Core; -using MaIN.Core.Hub; -using MaIN.Domain.Entities; -using OpenAI.Models; + using Examples.SimpleConsole; + using MaIN.Core; + using MaIN.Core.Hub; + using MaIN.Core.Hub.Utils; + using MaIN.Domain.Configuration; + using MaIN.NET.Examples.TogetherAI; -MaINBootstrapper.Initialize(); - -var model = AIHub.Model(); - -var m = model.GetModel("gemma3:4b"); -var x = model.GetModel("llama3.2:3b"); -await model.DownloadAsync(x.Name); + MaINBootstrapper.Initialize(); + Console.WriteLine("Image Agent - FLUX.1 schnell (~$0.028/image)"); + Console.WriteLine("Set TOGETHER_API_KEY and IMGBB_API_KEY environment variables"); + Console.WriteLine(); + var context = await AIHub.Agent() + .WithBackend(BackendType.GroqCloud) + .WithModel("meta-llama/llama-4-maverick-17b-128e-instruct") + .WithSteps(StepBuilder.Instance + .Answer() + .Build()) + .WithTools(new ToolsConfigurationBuilder() + .AddTool( + "generate_image", + "Generate an image from a text prompt. Returns imgbb URL.", + new + { + type = "object", + properties = new + { + prompt = new + { + type = "string", + description = "Description of the image to generate" + } + }, + required = new[] { "prompt" } + }, + ImageTools.GenerateImage) + .AddTool( + "edit_image", + "Edit an existing image using its URL. When user says 'add X to it' or similar, use the URL from the previous generate/edit response in this conversation.", + new + { + type = "object", + properties = new + { + imageUrl = new + { + type = "string", + description = "URL of the image to edit (from previous generate/edit or external URL)" + }, + prompt = new + { + type = "string", + description = "How to modify the image" + } + }, + required = new[] { "imageUrl", "prompt" } + }, + ImageTools.EditImage) + + .WithToolChoice("auto") + .Build()) + .CreateAsync(interactiveResponse: true); + // Example: Natural conversation flow with URLs + await context.ProcessAsync("Generate a cat"); + + Console.WriteLine("\n--//--\n"); + + // Claude should remember the URL from above and use it here + await context.ProcessAsync("Nice! Now add a wizard hat to it");