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