using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using xAiApi.Interfaces; using xCommons.Configurations; using xCommons.Controllers; using xCommons.Providers; namespace xAiApi.Controllers { [Authorize] [Route("[controller]")] public abstract class XAiServiceControllerBase : XBaseController, IXAiServiceControllerBase where TAiService : IXAiServiceBase { private readonly TAiService aiService; protected XAiServiceControllerBase( ILogger logger, TAiService aiService, XAppConfiguration appConfiguration, XValidationProvider validationProvider ) : base( logger, appConfiguration, validationProvider ) { this.aiService = aiService; } /// /// Ask Question and Get Response ... /// /// /// /// [AllowAnonymous] [HttpGet("AskText")] public virtual async Task> AskText( [FromQuery] string question, CancellationToken cancellationToken = default ) { // try { // // Validate ... ValidationProvider.NotEmpty(question); // var result = await aiService.AskTextAsync( prompt: question, cancellationToken: cancellationToken ); // return Ok(result); } catch (Exception ex) { // var result = GetExceptionActionResult(ex); return result; } } /// /// Ask Question and Get Response Stream ... /// /// /// /// [AllowAnonymous] [HttpGet("AskTextStream")] public virtual async Task AskTextStream( [FromQuery] string question, CancellationToken cancellationToken = default ) { // try { // // Validate ... ValidationProvider.NotEmpty(question); // // Configure SSE Response Headers ... Response.Headers["X-Accel-Buffering"] = "no"; Response.Headers["Connection"] = "keep-alive"; Response.Headers["Content-Type"] = "text/event-stream"; Response.Headers["Cache-Control"] = "no-cache, no-store"; // // Disable response buffering ... var responseBodyFeature = HttpContext.Features .Get(); responseBodyFeature?.DisableBuffering(); // // Stream Response ... var jsonOptions = new JsonSerializerOptions { Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; // var enumerable = aiService.AskTextAsEnumerable( prompt: question, cancellationToken: cancellationToken ); // await foreach (var chunk in enumerable) { // // Skip empty chunks ... if (string.IsNullOrEmpty(chunk)) { continue; } // // Build SSE message ... // Format: data: \n\n // var payload = JsonSerializer.Serialize( // new { text = chunk }, // jsonOptions // ); var payload = chunk; // // Write SSE Event ... await Response.WriteAsync($"{payload}\n\n", cancellationToken); await Response.Body.FlushAsync(cancellationToken); } // await Response.Body.FlushAsync(cancellationToken); } catch (Exception) { await Response.Body.FlushAsync(); } } } }