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+ }
0 commit comments