Skip to content

Commit 9d52e65

Browse files
committed
Add XaiImageGenService
1 parent b10ebcb commit 9d52e65

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using MaIN.Domain.Configuration;
2+
using MaIN.Domain.Entities;
3+
using MaIN.Services.Constants;
4+
using MaIN.Services.Services.Abstract;
5+
using MaIN.Services.Services.Models;
6+
using System.Net.Http.Headers;
7+
using System.Net.Http.Json;
8+
using System.Text.Json;
9+
10+
namespace MaIN.Services.Services.ImageGenServices;
11+
12+
public class XaiImageGenService(
13+
IHttpClientFactory httpClientFactory,
14+
MaINSettings settings)
15+
: IImageGenService
16+
{
17+
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
18+
private readonly MaINSettings _settings = settings ?? throw new ArgumentNullException(nameof(settings));
19+
20+
public async Task<ChatResult?> Send(Chat chat)
21+
{
22+
var client = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.XaiClient);
23+
string apiKey = _settings.XaiKey ?? Environment.GetEnvironmentVariable("XAI_API_KEY") ??
24+
throw new InvalidOperationException("xAI Key not configured");
25+
26+
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
27+
var requestBody = new
28+
{
29+
model = string.IsNullOrWhiteSpace(chat.Model) ? Models.GROK_IMAGE : chat.Model,
30+
prompt = BuildPromptFromChat(chat),
31+
n = 1,
32+
response_format = "b64_json" //or "url"
33+
};
34+
35+
using var response = await client.PostAsJsonAsync(ServiceConstants.ApiUrls.XaiImageGenerations, requestBody);
36+
var imageBytes = await ProcessXaiResponse(response);
37+
return CreateChatResult(imageBytes);
38+
}
39+
40+
private static string BuildPromptFromChat(Chat chat)
41+
{
42+
return chat.Messages
43+
.Select((msg, index) => index == 0 ? msg.Content : $"&& {msg.Content}")
44+
.Aggregate((current, next) => $"{current} {next}");
45+
}
46+
47+
private async Task<byte[]> ProcessXaiResponse(HttpResponseMessage response)
48+
{
49+
response.EnsureSuccessStatusCode();
50+
var responseData = await response.Content.ReadFromJsonAsync<XaiImageResponse>(new JsonSerializerOptions{ PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });
51+
52+
var first = responseData?.Data.FirstOrDefault()
53+
?? throw new InvalidOperationException("No image data returned from xAI");
54+
55+
if (!string.IsNullOrEmpty(first.B64Json))
56+
{
57+
return Convert.FromBase64String(first.B64Json);
58+
}
59+
60+
if (!string.IsNullOrEmpty(first.Url))
61+
{
62+
return await DownloadImageAsync(first.Url);
63+
}
64+
65+
throw new InvalidOperationException("No image content returned from xAI");
66+
}
67+
68+
private async Task<byte[]> DownloadImageAsync(string imageUrl)
69+
{
70+
var imageClient = _httpClientFactory.CreateClient(ServiceConstants.HttpClients.ImageDownloadClient);
71+
72+
using var imageResponse = await imageClient.GetAsync(imageUrl);
73+
imageResponse.EnsureSuccessStatusCode();
74+
75+
return await imageResponse.Content.ReadAsByteArrayAsync();
76+
}
77+
78+
private static ChatResult CreateChatResult(byte[] imageBytes)
79+
{
80+
return new ChatResult
81+
{
82+
Done = true,
83+
Message = new Message
84+
{
85+
Content = ServiceConstants.Messages.GeneratedImageContent,
86+
Role = ServiceConstants.Roles.Assistant,
87+
Image = imageBytes,
88+
Type = MessageType.Image
89+
},
90+
Model = Models.GROK_IMAGE,
91+
CreatedAt = DateTime.UtcNow
92+
};
93+
}
94+
95+
private struct Models
96+
{
97+
public const string GROK_IMAGE = "grok-2-image";
98+
}
99+
}
100+
101+
102+
file class XaiImageResponse
103+
{
104+
public XaiImageData[] Data { get; set; } = [];
105+
}
106+
107+
file class XaiImageData
108+
{
109+
public string? Url { get; set; }
110+
public string? B64Json { get; set; }
111+
public string? RevisedPrompt { get; set; }
112+
}

src/MaIN.Services/Services/LLMService/Factory/ImageGenServiceFactory.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class ImageGenServiceFactory(IServiceProvider serviceProvider) : IImageGe
1818
BackendType.DeepSeek => null,
1919
BackendType.GroqCloud => null,
2020
BackendType.Anthropic => null,
21+
BackendType.Xai => new XaiImageGenService(serviceProvider.GetRequiredService<IHttpClientFactory>(),
22+
serviceProvider.GetRequiredService<MaINSettings>()),
2123
BackendType.Self => new ImageGenService(serviceProvider.GetRequiredService<IHttpClientFactory>(),
2224
serviceProvider.GetRequiredService<MaINSettings>()),
2325

0 commit comments

Comments
 (0)