diff --git a/src/AltGen.Console/AltGen.Console.csproj b/src/AltGen.Console/AltGen.Console.csproj index b8472d7..d51b937 100644 --- a/src/AltGen.Console/AltGen.Console.csproj +++ b/src/AltGen.Console/AltGen.Console.csproj @@ -9,8 +9,11 @@ + + + diff --git a/src/AltGen.Console/Common/HostBuilderExtensions.cs b/src/AltGen.Console/Common/HostBuilderExtensions.cs new file mode 100644 index 0000000..2447aa0 --- /dev/null +++ b/src/AltGen.Console/Common/HostBuilderExtensions.cs @@ -0,0 +1,11 @@ +namespace AltGen.Console.Common; + +static class HostBuilderExtensions +{ + public static CommandApp BuildApp(this IHostBuilder builder) + { + var registrar = new TypeRegistrar(builder); + var app = new CommandApp(registrar); + return app; + } +} \ No newline at end of file diff --git a/src/AltGen.Console/Program.cs b/src/AltGen.Console/Program.cs index 7b33f13..e31f069 100644 --- a/src/AltGen.Console/Program.cs +++ b/src/AltGen.Console/Program.cs @@ -4,11 +4,159 @@ // and then post the image to our API // get the response and display the alt text -using Microsoft.Extensions.Hosting; +using System.ComponentModel; +using System.IO.Abstractions; +using System.Net.Http.Headers; + +using AltGen.Console.Common; + +using Spectre.Console; await Host.CreateDefaultBuilder(args) - .ConfigureServices(static (_, services) => { }) - .Build() // TODO: Need special extension for integrating host with Spectre.Console - .RunAsync(); + .ConfigureServices(static (_, services) => + { + services.AddSingleton(); + services.AddSingleton(AnsiConsole.Console); + services.AddHttpClient() + .AddStandardResilienceHandler(); + }) + .BuildApp() + .RunAsync(args); -Console.WriteLine("Hello, World!"); \ No newline at end of file +sealed class GenerateCommand( + IAnsiConsole console, + IFileSystem fileSystem, + IAltGenService altGenService +) : AsyncCommand +{ + + readonly IAnsiConsole _console = console; + readonly IFileSystem _fileSystem = fileSystem; + readonly IAltGenService _altGenService = altGenService; + + public class Settings(IFileSystem fileSystem) : CommandSettings + { + readonly Dictionary _imageTypes = new() + { + [".jpeg"] = "image/jpeg", + [".jpg"] = "image/jpeg", + [".png"] = "image/png" + }; + + readonly List _providers = [ + "gemini", + ]; + + readonly IFileSystem _fileSystem = fileSystem; + + [CommandArgument(1, "")] + [Description("The provider to use for generating alt text.")] + public string Provider { get; init; } = string.Empty; + + [CommandArgument(2, "")] + [Description("The key for the provider.")] + public string Key { get; init; } = string.Empty; + + [CommandArgument(3, "")] + [Description("The path to the image to generate alt text for.")] + public string Path { get; init; } = string.Empty; + + public string ContentType => _imageTypes[_fileSystem.Path.GetExtension(Path)]; + + public override ValidationResult Validate() + { + if (_providers.Contains(Provider) is false) + { + return ValidationResult.Error($"The provider '{Provider}' is not supported."); + } + + var pathExists = _fileSystem.File.Exists(Path); + + if (pathExists is false) + { + return ValidationResult.Error($"The path '{Path}' does not exist."); + } + + var extension = _fileSystem.Path.GetExtension(Path); + + if (_imageTypes.ContainsKey(extension) is false) + { + return ValidationResult.Error($"The file '{Path}' is not a valid image file."); + } + + return ValidationResult.Success(); + } + } + + public override async Task ExecuteAsync(CommandContext context, Settings settings) + { + var fileName = _fileSystem.Path.GetFileName(settings.Path); + var image = await _fileSystem.File.ReadAllBytesAsync(settings.Path); + + var altText = await _altGenService.GenerateAltTextAsync( + settings.Provider, + settings.Key, + fileName, + image, + settings.ContentType + ); + + _console.MarkupLine($"[bold]Alt Text:[/] {altText}"); + + return 0; + } +} + +interface IAltGenService +{ + Task GenerateAltTextAsync( + string provider, + string key, + string fileName, + byte[] image, + string contentType + ); +} + +sealed class AltGenService(HttpClient client) : IAltGenService +{ + readonly HttpClient _client = client; + + public async Task GenerateAltTextAsync( + string provider, + string key, + string fileName, + byte[] image, + string contentType + ) + { + var byteContent = new ByteArrayContent(image); + byteContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); + + // TODO: Need to have proper URL + // have constants generated at build time + // that point to proper API URL + var request = new HttpRequestMessage(HttpMethod.Post, "altgen") + { + Content = new MultipartFormDataContent + { + { new StringContent(provider), "provider" }, + { new StringContent(key), "providerKey" }, + { new ByteArrayContent(image), "file", fileName } + } + }; + + var response = await _client.SendAsync(request); + + if (response.IsSuccessStatusCode is false) + { + // TODO: Use accurate exception + throw new InvalidDataException("Failed to generate alt text."); + } + + // TODO: Deserialize the response + var altText = await response.Content.ReadAsStringAsync(); + + return altText; + } +} \ No newline at end of file